Hiểu sự khác biệt giữa __getattr__ và __getattribution__


206

Tôi đang cố gắng để hiểu sự khác biệt giữa __getattr____getattribute__, tuy nhiên, tôi đang thất bại ở đó.

Câu trả lời cho câu hỏi Stack Overflow Sự khác biệt giữa __getattr__vs__getattribute__ nói:

__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.

Tôi hoàn toàn không biết điều đó có nghĩa là gì.

Sau đó, nó tiếp tục nói:

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

Tại sao?

Tôi đọc rằng nếu __getattribute__thất bại, __getattr__được gọi. Vậy tại sao có hai phương pháp khác nhau làm cùng một việc? Nếu mã của tôi thực hiện các lớp kiểu mới, tôi nên sử dụng cái gì?

Tôi đang tìm kiếm một số ví dụ mã để xóa câu hỏi này. Tôi đã cố gắng hết khả năng của mình, nhưng câu trả lời mà tôi thấy không thảo luận kỹ về vấn đề này.

Nếu có bất kỳ tài liệu nào, tôi sẵn sàng để đọc nó.


2
nếu nó giúp, từ các tài liệu "Để 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). " [ Docs.python.org/2/reference/...
kmonsoor

Câu trả lời:


305

Một số điều cơ bản đầu tiên.

Với các đối tượng, bạn cần phải đối phó với các thuộc tính của nó. Thông thường chúng tôi làm instance.attribute. Đôi khi chúng ta cần kiểm soát nhiều hơn (khi chúng ta không biết trước tên của thuộc tính).

Ví dụ, instance.attributesẽ trở thành getattr(instance, attribute_name). Sử dụng mô hình này, chúng ta có thể lấy thuộc tính bằng cách cung cấp thuộc tính_name dưới dạng chuỗi.

Sử dụng __getattr__

Bạn cũng có thể nói với một lớp cách xử lý các thuộc tính mà nó không quản lý rõ ràng và thực hiện điều đó thông qua __getattr__phương thức.

Python sẽ gọi phương thức này bất cứ khi nào bạn yêu cầu một thuộc tính chưa được xác định, vì vậy bạn có thể xác định phải làm gì với nó.

Một trường hợp sử dụng cổ điển:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Hãy cẩn thận và sử dụng __getattribute__

Nếu bạn cần bắt mọi thuộc tính bất kể nó có tồn tại hay không , __getattribute__thay vào đó hãy sử dụng . Sự khác biệt là __getattr__chỉ được gọi cho các thuộc tính không thực sự tồn tại. Nếu bạn đặt thuộc tính trực tiếp, tham chiếu thuộc tính đó sẽ truy xuất thuộc tính đó mà không cần gọi __getattr__.

__getattribute__ được gọi là tất cả các lần.


"Ví dụ: instance.attribution sẽ trở thành getattr(instance, attribute_name).". Có nên không __getattribute__(instance, attribute_name)?
Md. Abu Nafee Ibna Zahid

1
@ Md.AbuNafeeIbnaZahid getattrlà một chức năng tích hợp. getattr(foo, 'bar')tương đương với foo.bar.
wizzwizz4

đáng lưu ý rằng __getattr__chỉ được gọi nếu được xác định, vì nó không được thừa kế từobject
joelb

94

__getattribute__ được gọi bất cứ khi nào một truy cập thuộc tính xảy ra.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Điều này sẽ gây ra đệ quy vô hạn. Thủ phạm ở đây là đường dây return self.__dict__[attr]. Hãy giả vờ (Nó đủ gần với sự thật) rằng tất cả các thuộc tính được lưu trữ self.__dict__và có sẵn theo tên của chúng. Dòng

f.a

cố gắng truy cập athuộc tính của f. Cuộc gọi này f.__getattribute__('a'). __getattribute__sau đó cố gắng tải self.__dict__. __dict__là một thuộc tính của self == fvà vì vậy các cuộc gọi python f.__getattribute__('__dict__')mà một lần nữa cố gắng truy cập vào thuộc tính '__dict__'. Đây là đệ quy vô hạn.

Nếu __getattr__đã được sử dụng thay thế thì

  1. Nó sẽ không bao giờ chạy vì fcó một athuộc tính.
  2. Nếu nó đã chạy, (giả sử bạn đã yêu cầu f.b) thì nó sẽ không được gọi để tìm __dict__vì nó đã ở đó và __getattr__chỉ được gọi nếu tất cả các phương pháp tìm thuộc tính khác không thành công .

Cách 'chính xác' để viết lớp trên bằng cách sử dụng __getattribute__

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)liên kết __getattribute__phương thức của siêu lớp 'gần nhất' (chính thức, lớp tiếp theo trong Thứ tự giải quyết phương thức của lớp hoặc MRO) với đối tượng hiện tại selfvà sau đó gọi nó và cho phép thực hiện công việc.

Tất cả những rắc rối này đều được tránh bằng cách sử dụng __getattr__cho phép Python làm điều bình thường cho đến khi không tìm thấy thuộc tính. Tại thời điểm đó, Python điều khiển __getattr__phương thức của bạn và cho phép nó đưa ra một cái gì đó.

Cũng đáng lưu ý rằng bạn có thể chạy theo đệ quy vô hạn __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Tôi sẽ để nó như một bài tập.


60

Tôi nghĩ rằng các câu trả lời khác đã làm rất tốt khi giải thích sự khác biệt giữa __getattr____getattribute__, nhưng một điều có thể không rõ ràng là tại sao bạn muốn sử dụng __getattribute__. Điều thú vị __getattribute__là về cơ bản nó cho phép bạn quá tải dấu chấm khi truy cập vào một lớp. Điều này cho phép bạn tùy chỉnh cách các thuộc tính được truy cập ở mức thấp. Chẳng hạn, giả sử tôi muốn định nghĩa một lớp trong đó tất cả các phương thức chỉ lấy một đối số tự được coi là các thuộc tính:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

Và từ trình thông dịch tương tác:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Tất nhiên đây là một ví dụ ngớ ngẩn và có lẽ bạn sẽ không bao giờ muốn làm điều này, nhưng nó cho bạn thấy sức mạnh bạn có thể nhận được từ việc ghi đè __getattribute__.


6

Tôi đã trải qua lời giải thích tuyệt vời khác. Tuy nhiên, tôi đã tìm thấy một câu trả lời đơn giản từ blog này Phương pháp ma thuật Python và__getattr__ . Tất cả những điều sau đây là từ đó.

Sử dụng __getattr__phương pháp ma thuật, chúng ta có thể chặn việc tra cứu thuộc tính không tồn tại đó và làm một cái gì đó để nó không thất bại:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Nhưng nếu thuộc tính tồn tại, __getattr__sẽ không được gọi:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__tương tự như __getattr__, với sự khác biệt quan trọng __getattribute__sẽ chặn MỌI tra cứu thuộc tính, không quan trọng nếu thuộc tính có tồn tại hay không.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

Trong ví dụ đó, dđối tượng đã có một giá trị thuộc tính. Nhưng khi chúng tôi cố gắng truy cập nó, chúng tôi không nhận được giá trị mong đợi ban đầu (Python Python,); chúng tôi chỉ nhận được bất cứ điều gì __getattribute__trở lại. Điều đó có nghĩa là chúng ta hầu như đã mất thuộc tính giá trị; nó đã trở thành không thể truy cập đượ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.