2011-01-05 43 views
5

Hãy nhìn vào đoạn mã sau:Làm cách nào để truy cập các thuộc tính lớp của lớp cha trong Python?

class A(object): 
    defaults = {'a': 1} 

    def __getattr__(self, name): 
     print('A.__getattr__') 
     return self.get_default(name) 

    @classmethod 
    def get_default(cls, name): 
     # some debug output 
     print('A.get_default({}) - {}'.format(name, cls)) 
     try: 
      print(super(cls, cls).defaults) # as expected 
     except AttributeError: #except for the base object class, of course 
      pass 

     # the actual function body 
     try: 
      return cls.defaults[name] 
     except KeyError: 
      return super(cls, cls).get_default(name) # infinite recursion 
      #return cls.__mro__[1].get_default(name) # this works, though 

class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c = C() 
print('c.a =', c.a) 

tôi có một hệ thống các lớp học mỗi từ điển riêng của mình có chứa một số giá trị mặc định. Nếu một thể hiện của một lớp không có một thuộc tính cụ thể, thay vào đó một giá trị mặc định cho lớp đó sẽ được trả về. Nếu không có giá trị mặc định cho thuộc tính được chứa trong từ điển hiện tại của lớp defaults, từ điển của lớp học defaults sẽ được tìm kiếm.

Tôi đang cố thực hiện điều này bằng phương pháp lớp đệ quy get_default. Chương trình bị mắc kẹt trong một đệ quy vô hạn, thật không may. Sự hiểu biết của tôi về super() rõ ràng là thiếu. Bằng cách truy cập __mro__, tôi có thể làm cho nó hoạt động đúng mặc dù, nhưng tôi không chắc chắn đây là một giải pháp thích hợp.

Tôi có cảm giác câu trả lời ở đâu đó trong số this article, nhưng tôi chưa thể tìm thấy câu trả lời. Có lẽ tôi cần phải sử dụng một metaclass?

chỉnh sửa: Trong đơn đăng ký của tôi, __getattr__ kiểm tra đầu tiên self.base. Nếu nó không phải là None, thuộc tính cần được tìm nạp từ đó. Chỉ trong trường hợp khác, giá trị mặc định phải được trả về. Tôi có thể có thể ghi đè lên __getattribute__. Đó có phải là giải pháp tốt hơn không?

chỉnh sửa 2: Dưới đây là ví dụ mở rộng về chức năng mà tôi đang tìm kiếm. Nó hiện đang được thực hiện bằng cách sử dụng __mro__ (đề xuất trước đó của unutbu, trái với phương pháp đệ quy ban đầu của tôi). Trừ khi ai đó có thể đề xuất một giải pháp thanh lịch hơn, tôi rất vui khi sử dụng triển khai này. Tôi hy vọng điều này sẽ làm mọi thứ rõ ràng.

class A(object): 
    defaults = {'a': 1} 

    def __init__(self, name, base=None): 
     self.name = name 
     self.base = base 

    def __repr__(self): 
     return self.name 

    def __getattr__(self, name): 
     print(" '{}' attribute not present in '{}'".format(name, self)) 
     if self.base is not None: 
      print(" getting '{}' from base ({})".format(name, self.base)) 
      return getattr(self.base, name) 
     else: 
      print(" base = None; returning default value") 
      return self.get_default(name) 

    def get_default(self, name): 
     for cls in self.__class__.__mro__: 
      try: 
       return cls.defaults[name] 
      except KeyError: 
       pass 
     raise KeyError 

class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c1 = C('c1') 
c1.b = 55 

print('c1.a = ...'); print(' ...', c1.a) # 1 
print(); print('c1.b = ...'); print(' ...', c1.b) # 55 
print(); print('c1.c = ...'); print(' ...', c1.c) # 3 

c2 = C('c2', base=c1) 
c2.c = 99 

print(); print('c2.a = ...'); print(' ...', c2.a) # 1 
print(); print('c2.b = ...'); print(' ...', c2.b) # 55 
print(); print('c2.c = ...'); print(' ...', c2.c) # 99 

Sản lượng:

c1.a = ... 
'a' attribute not present in 'c1' 
    base = None; returning default value 
    ... 1 

c1.b = ... 
    ... 55 

c1.c = ... 
'c' attribute not present in 'c1' 
    base = None; returning default value 
    ... 3 

c2.a = ... 
'a' attribute not present in 'c2' 
    getting 'a' from base (c1) 
'a' attribute not present in 'c1' 
    base = None; returning default value 
    ... 1 

c2.b = ... 
'b' attribute not present in 'c2' 
    getting 'b' from base (c1) 
    ... 55 

c2.c = ... 
    ... 99 
+4

Làm cách nào để đặt mặc định trực tiếp vào không gian tên lớp thay vì sử dụng từ điển 'mặc định'? Điều này sẽ làm cho bất kỳ ma thuật không cần thiết - nó sẽ chỉ hoạt động. –

+0

@Sven Tôi biết tôi nên đã đề cập đến điều này :) Trong ứng dụng của tôi, '__getattr__' là lần đầu tiên kiểm tra một thuộc tính (cơ sở). Chỉ khi thuộc tính kia là None, giá trị mặc định sẽ được trả về. Nếu không, thuộc tính cần phải được tìm kiếm trong cơ sở. –

+0

Vâng, đó là cách các thuộc tính instance/class hoạt động trong Python. :) Kiểm tra câu trả lời của Toni. Afaik mã của mình sẽ làm chính xác giống như của bạn. –

Trả lời

0

Giải pháp được đề xuất trong bản chỉnh sửa thứ hai của câu hỏi vẫn là giải pháp duy nhất cung cấp mọi thứ mà ứng dụng của tôi yêu cầu. Mặc dù mã của unutbu có thể đơn giản hơn để hiểu, giải pháp __mro__ cung cấp một số lợi thế IMO (xem nhận xét).

0

Bạn nên sử dụng name mangling đây.

Rename defaults-__defaults

Điều đó cho mỗi lớp một thuộc tính rõ ràng để họ không bị nhầm lẫn với nhau

8

Không thực sự là một câu trả lời nhưng một quan sát:

này trông overengineered với tôi, một bẫy thông thường khi tìm kiếm lý do để sử dụng ma thuật python.

Nếu bạn có thể bị làm phiền để xác định defaults dict cho một lớp học tại sao không chỉ xác định các thuộc tính thay thế? Hiệu quả là như nhau.

class A: 
    a = 1 

class B(A): 
    b = 2 

class C(B): 
    c = 3 


c = C() 
print('c.a =', c.a) 

EDIT:

Đối với trả lời câu hỏi, tôi sẽ có thể sử dụng __getattribute__ kết hợp với gợi ý của tôi như thế này:

def __getattribute__(self, name): 
    try: 
     return object.__getattribute__(self.base, name) 
    except AttributeError: 
     return object.__getattribute__(self, name) 
+0

Điều đáng nói là bạn cũng có thể đặt thuộc tính trên c. Vì vậy, bạn trong thực tế có được một thiết lập cá thể, với một mặc định được định nghĩa trong lớp. –

+0

Điều này sẽ không phục vụ cho trường hợp sử dụng mà tôi đã thêm vào câu hỏi. –

+0

@Brecht Machiels: Đúng vậy. Thử nó. –

1

Làm thế nào về:

class A(object): 
    def __init__(self,base=None): 
     self.a=1 
     if base is not None: 
      self.set_base(base) 
     super(A,self).__init__() 
    def set_base(self,base): 
     for key in ('a b c'.split()): 
      setattr(self,key,getattr(base,key)) 
class B(A): 
    def __init__(self,base=None): 
     self.b=2 
     super(B,self).__init__(base)   
class C(B): 
    def __init__(self,base=None): 
     self.c=3 
     super(C,self).__init__(base) 

c1=C() 
c1.b=55 
print(c1.a) 
print(c1.b) 
print(c1.c) 
# 1 
# 55 
# 3 

c2=C(c1) 
c2.c=99 
print(c2.a) 
print(c2.b) 
print(c2.c) 
# 1 
# 55 
# 99 

c1.set_base(c2) 
print(c1.a) 
print(c1.b) 
print(c1.c) 
# 1 
# 55 
# 99 
+0

Nếu cơ số là * không * 'None', nó cần trả về thuộc tính tương ứng của cơ số. Nhưng tôi đoán chúng ta không thể lấy tên của thuộc tính trong hàm wrapper? –

+0

@Brecht Machiels: Có thể truyền các tham số phụ vào hàm 'check_base_first', có thể được truy cập từ' wrapper'. – unutbu

+1

@Brecht Machiels: Mặc dù có thể có một cách để sử dụng các thuộc tính để làm những gì bạn muốn, nó có thể không phải là giải pháp đúng. Vấn đề bạn đang phải đối mặt có thể là một triệu chứng của một quyết định thiết kế tồi tệ trước đó. Nếu bạn sửa đổi câu hỏi của mình để hiển thị chính xác cách bạn muốn đối tượng của bạn cư xử (với 'base') có lẽ một thiết kế tốt hơn có thể được đề xuất. – unutbu

2

Tôi nghĩ rằng kết quả sự cố từ misun bắt nguồn từ mục đích của super().

http://docs.python.org/library/functions.html#super

Về cơ bản, gói đối tượng của bạn (hoặc lớp) trong siêu() làm cho Python bỏ lớp gần đây nhất được thừa kế khi thực hiện tra cứu thuộc tính. Trong mã của bạn, kết quả này trong lớp C bị bỏ qua khi tìm kiếm get_default, nhưng điều này không thực sự làm bất cứ điều gì, vì C không định nghĩa một get_default. Đương nhiên, điều này dẫn đến một vòng lặp vô hạn.

Giải pháp là để xác định chức năng này trong mỗi lớp học có nguồn gốc từ A. Điều này có thể được thực hiện bằng một metaclass:

class DefaultsClass(type): 
    def __init__(cls, name, bases, dct): 

     def get_default(self, name): 
      # some debug output 
      print('A.get_default(%s) - %s' % (name, cls)) 
      try: 
       print(cls.defaults) # as expected 
      except AttributeError: #except for the base object class, of course 
       pass 

      # the actual function body 
      try: 
       return cls.defaults[name] 
      except KeyError: 
       return super(cls, self).get_default(name) # cooperative superclass 

     cls.get_default = get_default 
     return super(DefaultsClass, cls).__init__(name, bases, dct) 

class A(object): 
    defaults = {'a': 1} 
    __metaclass__ = DefaultsClass 

    def __getattr__(self, name): 
     return self.get_default(name) 



class B(A): 
    defaults = {'b': 2} 

class C(B): 
    defaults = {'c': 3} 


c = C() 
print('c.a =', c.a) 
print('c.b =', c.b) 
print('c.c =', c.c) 

kết quả:

A.get_default(c) - <class '__main__.C'> 
{'c': 3} 
('c.c =', 3) 
A.get_default(b) - <class '__main__.C'> 
{'c': 3} 
A.get_default(b) - <class '__main__.B'> 
{'b': 2} 
('c.b =', 2) 
A.get_default(a) - <class '__main__.C'> 
{'c': 3} 
A.get_default(a) - <class '__main__.B'> 
{'b': 2} 
A.get_default(a) - <class '__main__.A'> 
{'a': 1} 
('c.a =', 1) 

tôi nên lưu ý rằng hầu hết Python mọi người sẽ coi đây là một giải pháp rất kì lạ và bạn chỉ nên sử dụng giải pháp này nếu bạn thực sự cần, có thể hỗ trợ mã cũ.

1

Để rõ ràng hơn về trường hợp "cơ bản" so với "mặc định" của bạn.

>>> class A(object): 
...  a = 1 
... 
>>> class B(A): 
...  b = 2 
... 
>>> class C(B): 
...  c = 3 
... 
>>> a = A() 
>>> b = B() 
>>> c = C() 
>>> 
>>> b.b = 23 
>>> b.a 
1 
>>> b.b 
23 
>>> c.a 
1 
>>> c.b 
2 
>>> c.c 
3 
>>> c.c = 45 
>>> c.c 
45 

Điều này bao gồm trường hợp sử dụng đã nêu của bạn. Bạn không cần phép thuật chút nào. Nếu usecase của bạn là hơi khác nhau, giải thích nó là gì, và chúng tôi sẽ cho bạn biết làm thế nào để làm điều đó mà không có magick. ;)

Các vấn đề liên quan