Розуміння різниці між __getattr__ та __getattribute__


206

Я намагаюся зрозуміти різницю між __getattr__і __getattribute__, тим НЕ менш, я впевнений , не в цьому.

Відповідь на питання переповнення стека Різниця між __getattr__VS__getattribute__ каже:

__getattribute__викликається перед тим, як переглянути фактичні атрибути на об’єкті, і тому може бути складним для правильної реалізації. Ви можете виявитись нескінченними рекурсіями дуже легко.

Я абсолютно не маю уявлення, що це означає.

Потім він продовжує говорити:

Ви майже напевно хочете __getattr__.

Чому?

Я читав, що якщо __getattribute__не вдається, __getattr__викликається. То чому два різні методи роблять одне і те ж? Якщо мій код реалізує нові класи стилів, що я повинен використовувати?

Я шукаю кілька прикладів коду, щоб очистити це питання. Я гуглив все можливе, але знайдені відповіді не обговорювали проблему грунтовно.

Якщо є якась документація, я готовий її прочитати.


2
якщо це допомагає, від docs "Щоб уникнути нескінченної рекурсії в цьому методі, його реалізація завжди повинна викликати метод базового класу з тим самим іменем для доступу до будь-яких необхідних йому атрибутів, наприклад, об’єкта .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor

Відповіді:


306

Перші основи.

З об’єктами потрібно розібратися з його атрибутами. Зазвичай ми робимо instance.attribute. Іноді нам потрібен більший контроль (коли ми не знаємо назви атрибуту заздалегідь).

Наприклад, instance.attributeстане getattr(instance, attribute_name). Використовуючи цю модель, ми можемо отримати атрибут, надавши ім'я attribute_name як рядок.

Використання __getattr__

Ви також можете сказати класу, як поводитися з атрибутами, якими він прямо не керує, і зробити це за допомогою __getattr__методу.

Python буде викликати цей метод кожного разу, коли ви запитаєте атрибут, який ще не був визначений, тож ви можете визначити, що з ним робити.

Класичний випадок використання:

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

Застереження та використання __getattribute__

Якщо вам потрібно зловити кожен атрибут незалежно від того, існує він чи ні , скористайтеся __getattribute__натомість. Різниця полягає в тому, що __getattr__викликаються лише атрибути, які насправді не існують. Якщо ви встановите атрибут безпосередньо, посилання на цей атрибут отримає його без виклику __getattr__.

__getattribute__ називається весь час.


"Наприклад, instance.attribute стане getattr(instance, attribute_name)." Чи не повинно бути __getattribute__(instance, attribute_name)?
Пані Абу Нафей Ібна Захід

1
@ Md.AbuNafeeIbnaZahid getattr- це вбудована функція. getattr(foo, 'bar')еквівалентно foo.bar.
wizzwizz4

Варто зазначити, що __getattr__називається лише тоді, коли він визначений, оскільки він не успадкований відobject
joelb

94

__getattribute__ викликається кожен раз, коли відбувається доступ до атрибутів.

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

Це спричинить нескінченну рекурсію. Винуватець тут - лінія return self.__dict__[attr]. Давайте зробимо вигляд, що (це досить близько до істини), що всі атрибути зберігаються self.__dict__і доступні за їх іменем. Лінія

f.a

спроби отримати доступ до aатрибуту f. Це дзвінки f.__getattribute__('a'). __getattribute__потім намагається завантажити self.__dict__. __dict__є атрибутом self == fі так називає python, f.__getattribute__('__dict__')який знову намагається отримати доступ до атрибута '__dict__'. Це нескінченна рекурсія.

Якщо __getattr__замість цього використовувались

  1. Він ніколи не біг, бо fмає aатрибут.
  2. Якби він запустився (скажімо, що ви просили f.b), його не закликали б знайти, __dict__тому що він уже є, і __getattr__його викликають лише у тому випадку, якщо всі інші методи пошуку атрибута не вдалися .

«Правильний» спосіб писати вище клас , використовуючи __getattribute__це

class Foo(object):
    # Same __init__

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

super(Foo, self).__getattribute__(attr)прив'язує __getattribute__метод "найближчого" суперкласу (формально, наступного класу в наказі методу роздільної здатності класу або MRO) до поточного об'єкта, selfа потім викликає його і дозволяє виконувати роботу.

Всю цю проблему можна уникнути, використовуючи __getattr__який дозволяє Python робити це нормальною справою, поки атрибут не знайдеться. У цей момент Python передає контроль над вашим __getattr__методом і дозволяє йому щось придумати.

Також варто зазначити, що ви можете зіткнутися з нескінченною рекурсією __getattr__.

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

Я залишу це як вправу.


60

Я думаю, що інші відповіді зробили чудову роботу, пояснивши різницю між __getattr__і __getattribute__, але одне, що може бути незрозумілим, - це чому б ви хотіли використовувати __getattribute__. Класна річ у __getattribute__тому, що вона по суті дозволяє перевантажувати крапку під час доступу до класу. Це дозволяє налаштувати доступ до атрибутів на низькому рівні. Наприклад, припустимо, я хочу визначити клас, де всі методи, які приймають лише аргумент, трактуються як властивості:

# 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

І від інтерактивного перекладача:

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

Звичайно, це дурний приклад, і ви, мабуть, ніколи не хотіли б цього робити, але це показує вам силу, яку ви можете отримати від переосмислення __getattribute__.


6

Я пережив чудове пояснення інших. Однак я знайшов просту відповідь з цього блогу Magic Methods Python і__getattr__ . Все наступне - звідти.

Використовуючи __getattr__магічний метод, ми можемо перехопити цей неіснуючий пошук атрибутів і зробити щось, щоб воно не вийшло з ладу:

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'

Але якщо атрибут існує, __getattr__виклик не буде:

class Dummy(object):

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

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

__getattribute__подібний до того __getattr__, що важлива відмінність, яка __getattribute__перехопить ВСЯКОЙ пошук атрибутів, не має значення, існує атрибут чи ні.

class Dummy(object):

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

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

У цьому прикладі dоб'єкт вже має значення атрибута. Але коли ми намагаємося отримати доступ до нього, ми не отримуємо початкового очікуваного значення ("Python"); ми просто отримуємо все, що __getattribute__повертається. Це означає, що ми практично втратили атрибут значення; це стало "недосяжним".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.