Tôi đang cố gắng để hiểu khi nào nên sử dụng __getattr__
hay __getattribute__
. Các tài liệu đề cập __getattribute__
áp dụng cho các lớp phong cách mới. Các lớp học kiểu mới là gì?
Tôi đang cố gắng để hiểu khi nào nên sử dụng __getattr__
hay __getattribute__
. Các tài liệu đề cập __getattribute__
áp dụng cho các lớp phong cách mới. Các lớp học kiểu mới là gì?
Câu trả lời:
Sự khác biệt chính giữa __getattr__
và __getattribute__
là __getattr__
duy nhất được gọi nếu thuộc tính không được tìm thấy trong những cách thông thường. Nó tốt cho việc thực hiện dự phòng cho các thuộc tính bị thiếu và có lẽ là một trong hai bạn muốn.
__getattribute__
được gọi trước khi xem xét các thuộc tính thực tế trên đối tượng và do đó có thể khó thực hiện chính xác. Bạn có thể kết thúc trong thu hồi vô hạn rất dễ dàng.
Các lớp kiểu mới xuất phát từ object
, các lớp kiểu cũ là các lớp trong Python 2.x không có lớp cơ sở rõ ràng. Nhưng sự khác biệt giữa các lớp kiểu cũ và kiểu mới không phải là thứ quan trọng khi chọn giữa __getattr__
và __getattribute__
.
Bạn gần như chắc chắn muốn __getattr__
.
__getattribute__
sẽ được gọi cho mọi quyền truy cập và __getattr__
sẽ được gọi cho những lần __getattribute__
tăng AttributeError
. Tại sao không chỉ giữ tất cả trong một?
__getattribute__
.
objec.__getattribute__
cầu khẩn myclass.__getattr__
trong hoàn cảnh phù hợp.
Hãy xem một số ví dụ đơn giản về cả hai __getattr__
và __getattribute__
phương pháp ma thuật.
__getattr__
Python sẽ gọi __getattr__
phương thức bất cứ khi nào bạn yêu cầu một thuộc tính chưa được xác định. Trong ví dụ sau lớp học của tôi Đếm không có __getattr__
phương pháp. Bây giờ trong chính khi tôi cố gắng truy cập cả hai obj1.mymin
và obj1.mymax
thuộc tính mọi thứ hoạt động tốt. Nhưng khi tôi cố gắng truy cập obj1.mycurrent
thuộc tính - Python cho tôiAttributeError: 'Count' object has no attribute 'mycurrent'
class Count():
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent) --> AttributeError: 'Count' object has no attribute 'mycurrent'
Bây giờ lớp học của tôi Đếm có __getattr__
phương pháp. Bây giờ khi tôi cố gắng truy cập obj1.mycurrent
thuộc tính - python trả về cho tôi bất cứ điều gì tôi đã thực hiện trong __getattr__
phương thức của mình . Trong ví dụ của tôi bất cứ khi nào tôi cố gắng gọi một thuộc tính không tồn tại, python tạo thuộc tính đó và đặt nó thành giá trị nguyên 0.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
def __getattr__(self, item):
self.__dict__[item]=0
return 0
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)
__getattribute__
Bây giờ hãy xem __getattribute__
phương thức. Nếu bạn có __getattribute__
phương thức trong lớp, python gọi phương thức này cho mọi thuộc tính bất kể nó có tồn tại hay không. Vậy tại sao chúng ta cần __getattribute__
phương pháp? Một lý do chính là bạn có thể ngăn truy cập vào các thuộc tính và làm cho chúng an toàn hơn như trong ví dụ sau.
Bất cứ khi nào ai đó cố gắng truy cập các thuộc tính của tôi bắt đầu bằng chuỗi con trăn 'cur', chuỗi AttributeError
ngoại lệ sẽ tăng ngoại lệ. Nếu không, nó trả về thuộc tính đó.
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
Quan trọng: Để tránh đệ quy vô hạn trong __getattribute__
phương thức, việc triển khai nó phải luôn gọi phương thức lớp cơ sở có cùng tên để truy cập bất kỳ thuộc tính nào mà nó cần. Ví dụ: object.__getattribute__(self, name)
hoặc super().__getattribute__(item)
khôngself.__dict__[item]
Nếu lớp của bạn chứa cả phương thức ma thuật getattr và getattribution thì __getattribute__
được gọi đầu tiên. Nhưng nếu __getattribute__
tăng
AttributeError
ngoại lệ thì ngoại lệ sẽ bị bỏ qua và __getattr__
phương thức sẽ được gọi. Xem ví dụ sau:
class Count(object):
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattr__(self, item):
self.__dict__[item]=0
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
# note this class subclass object
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
__getattribute__
nhưng chắc chắn đây không phải là trường hợp . Bởi vì theo ví dụ của bạn, tất cả những gì bạn đang làm __getattribute__
là đưa ra AttributeError
ngoại lệ nếu thuộc tính không có trong __dict__
đối tượng; nhưng bạn không thực sự cần điều này bởi vì đây là cài đặt mặc định __getattribute__
và nguyên vẹn __getattr__
chính xác là những gì bạn cần như một cơ chế dự phòng.
current
được định nghĩa trong các trường hợp của Count
(xem __init__
), do đó, chỉ cần tăng AttributeError
nếu thuộc tính không có gì xảy ra - nó sẽ làm __getattr__
cho tất cả các tên bắt đầu 'cur', bao gồm current
, nhưng curious
, curly
...
Đây chỉ là một ví dụ dựa trên lời giải thích của Ned Batchelder .
__getattr__
thí dụ:
class Foo(object):
def __getattr__(self, attr):
print "looking up", attr
value = 42
self.__dict__[attr] = value
return value
f = Foo()
print f.x
#output >>> looking up x 42
f.x = 3
print f.x
#output >>> 3
print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')
Và nếu ví dụ tương tự được sử dụng với __getattribute__
Bạn sẽ nhận được >>>RuntimeError: maximum recursion depth exceeded while calling a Python object
__getattr__()
triển khai trong thế giới thực chỉ chấp nhận một tập hợp hữu hạn các tên thuộc tính hợp lệ bằng cách tăng AttributeError
các tên thuộc tính không hợp lệ, do đó tránh các vấn đề khó xử lý và khó gỡ lỗi . Ví dụ này vô điều kiện chấp nhận tất cả các tên thuộc tính là hợp lệ - một sự lạm dụng kỳ lạ (và dễ bị lỗi) __getattr__()
. Nếu bạn muốn "kiểm soát toàn bộ" đối với việc tạo thuộc tính như trong ví dụ này, bạn muốn __getattribute__()
thay thế.
defaultdict
.
__getattr__
sẽ được gọi trước khi tra cứu siêu lớp. Điều này là ổn đối với một lớp con trực tiếp object
, vì các phương thức duy nhất bạn thực sự quan tâm có các phương thức ma thuật dù sao cũng bỏ qua thể hiện, nhưng đối với bất kỳ cấu trúc thừa kế phức tạp nào, bạn loại bỏ hoàn toàn khả năng kế thừa bất cứ thứ gì từ cha mẹ.
Các lớp kiểu mới kế thừa từ object
hoặc từ một lớp kiểu mới khác:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
Các lớp học kiểu cũ không:
class SomeObject:
pass
Điều này chỉ áp dụng cho Python 2 - trong Python 3, tất cả những điều trên sẽ tạo ra các lớp kiểu mới.
Xem 9. Các lớp (hướng dẫn Python), NewClassVsClassicClass và sự khác biệt giữa kiểu cũ và lớp kiểu mới trong Python là gì? để biết chi tiết.
Các lớp kiểu mới là các lớp con "đối tượng" (trực tiếp hoặc gián tiếp). Họ có một __new__
phương thức lớp ngoài __init__
và có một số hành vi cấp thấp hợp lý hơn.
Thông thường, bạn sẽ muốn ghi đè __getattr__
(nếu bạn ghi đè ), nếu không, bạn sẽ gặp khó khăn khi hỗ trợ cú pháp "self.foo" trong các phương thức của mình.
Thông tin thêm: http://www.devx.com/opensource/Article/31482/0/page/4
Khi đọc qua Beazley & Jones PCB, tôi đã vấp phải một trường hợp sử dụng rõ ràng và thực tế để __getattr__
giúp trả lời phần "khi nào" trong câu hỏi của OP. Từ cuốn sách:
" __getattr__()
Phương thức này giống như một phương thức tra cứu thuộc tính. Đó là một phương thức được gọi nếu mã cố truy cập vào một thuộc tính không tồn tại." Chúng tôi biết điều này từ các câu trả lời trên, nhưng trong công thức PCB 8.15, chức năng này được sử dụng để thực hiện mẫu thiết kế ủy quyền . Nếu Đối tượng A có thuộc tính Đối tượng B thực hiện nhiều phương thức mà Đối tượng A muốn ủy quyền, thay vì xác định lại tất cả các phương thức của Đối tượng B trong Đối tượng A chỉ để gọi các phương thức của Đối tượng B, hãy xác định __getattr__()
phương thức như sau:
def __getattr__(self, name):
return getattr(self._b, name)
Trong đó _b là tên của thuộc tính Object A là Object B. Khi một phương thức được xác định trên Object B được gọi trên Object A, __getattr__
phương thức sẽ được gọi ở cuối chuỗi tra cứu. Điều này cũng sẽ làm cho mã sạch hơn vì bạn không có danh sách các phương thức được xác định chỉ để ủy quyền cho đối tượng khác.