Понимание разницы между __getattr__ и __getattribute__

206

Я пытаюсь понять разницу между ними, __getattr__и __getattribute__, тем не менее, мне это не удается.

Ответ на вопрос переполнения стека Разница между __getattr__VS__getattribute__ говорит:

__getattribute__вызывается перед просмотром фактических атрибутов объекта, и поэтому может быть сложно реализовать правильно. Вы можете очень легко попасть в бесконечные рекурсии.

Я понятия не имею, что это значит.

Тогда это говорит:

Вы почти наверняка хотите __getattr__.

Зачем?

Я читал, что если __getattribute__не получается, __getattr__называется. Так почему же два разных метода делают одно и то же? Если мой код реализует новые классы стилей, что я должен использовать?

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

Если есть какая-либо документация, я готов это прочитать.

user225312
источник
2
если это помогает, из документации "Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым необходимым ему атрибутам, например, объекту .__ getattribute __ (self, name). " [ docs.python.org/2/reference/…
kmonsoor

Ответы:

307

Сначала некоторые основы.

С объектами вам нужно разобраться с его атрибутами. Обычно мы делаем 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__ называется все время.

pyfunc
источник
Msgstr "Например, стал бы экземпляр instance.attribute getattr(instance, attribute_name)." Не должно ли это быть __getattribute__(instance, attribute_name)?
Г-н Абу Нафи Ибна Захид
1
@ Md.AbuNafeeIbnaZahid getattr- это встроенная функция. getattr(foo, 'bar')эквивалентно foo.bar.
wizzwizz4
Стоит отметить, что __getattr__он object
вызывается
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

Я оставлю это как упражнение.

aaronasterling
источник
60

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

# 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

Я прошел через отличное объяснение других. Тем не менее, я нашел простой ответ из этого блога Python Magic Methods и__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__вернули. Это означает, что мы фактически потеряли атрибут value; оно стало «недоступным».

mathsyouth
источник