Sự khác biệt giữa __getattr__ so với __getattribution__


418

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ì?



5
@Trilarion nhưng câu hỏi đó đề cập đến một câu trả lời từ đây ...
JC Rocamonde

Câu trả lời:


497

Sự khác biệt chính giữa __getattr____getattribute____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____getattribute__.

Bạn gần như chắc chắn muốn __getattr__.


6
Tôi có thể thực hiện cả hai trong cùng một lớp không? Nếu tôi có thể, nó là gì để thực hiện cả hai?
Alcott

13
@ Alcott: bạn có thể thực hiện cả hai, nhưng tôi không chắc tại sao bạn lại làm như vậy. __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?
Ned Batchelder

10
@NedBatchelder, nếu bạn muốn (có điều kiện) ghi đè các cuộc gọi đến các phương thức hiện có, bạn sẽ muốn sử dụng __getattribute__.
Jace Browning

28
"Để tránh đệ quy vô hạn trong phương thức này, 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ụ, đối tượng .__ getattribution __ (self, name)."
kmonsoor

1
@kmonsoor. Đó là một lý do tốt để thực hiện cả hai. objec.__getattribute__cầu khẩn myclass.__getattr__trong hoàn cảnh phù hợp.
Nhà vật lý điên

138

Hãy xem một số ví dụ đơn giản về cả hai __getattr____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.myminobj1.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__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]

QUAN TRỌNG

Nếu lớp của bạn chứa cả phương thức ma thuật getattrgetattribution 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)

1
Tôi không chắc chính xác trường hợp nào sẽ được sử dụng để ghi đè __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.
Rohit

@Rohit 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...
joelb

16

Đâ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


9
Thật ra, điều này thật kinh khủng. Việc __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ế.
Cecil Curry

3
@CecilCurry: Tất cả các vấn đề mà bạn liên kết liên quan đến việc hoàn toàn trả lại Không có gì hơn là một giá trị, điều mà câu trả lời này không làm được. Có gì sai khi chấp nhận tất cả các tên thuộc tính? Nó giống như một defaultdict.
Nick Matteo

2
Vấn đề là nó __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ẹ.
Nhà vật lý điên

1
@Simon K Bhatta4ya, tuyên bố in cuối cùng của bạn là một nhận xét. Đúng? Đó là một dòng dài và tẻ nhạt để đọc (Người ta phải cuộn rất nhiều ở phía bên phải). Điều gì về việc đặt dòng sau phần mã? Hoặc nếu bạn muốn đặt nó trong phần mã, tôi nghĩ sẽ tốt hơn nếu chia nó thành hai dòng khác nhau.
Md. Abu Nafee Ibna Zahid

14

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), NewClassVsClassicClasssự 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.


6

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


2

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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.