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.myminvà obj1.mymaxthuộc tính mọi thứ hoạt động tốt. Nhưng khi tôi cố gắng truy cập obj1.mycurrentthuộ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.mycurrentthuộ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 AttributeErrorngoạ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
AttributeErrorngoạ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 AttributeErrorngoạ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 AttributeErrornế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 AttributeErrorcá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ừ objecthoặ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.