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

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

Вопрос о переполнении стека Разница между getattr и getattribute гласит:

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

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

Затем он продолжает:

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

Почему?

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

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

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

Ответ 1

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

С объектами вам нужно иметь дело с его атрибутами. Обычно мы делаем instance.attribute. Иногда нам нужно больше контроля (когда мы не знаем имя атрибута заранее).

Например, instance.attribute станет getattr(instance, 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__ вызывается все время.

Ответ 2

__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__, то

  • Он никогда не запускался бы, потому что f имеет атрибут a.
  • Если он запустился (скажем, что вы попросили 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__ "ближайшего" суперкласса (формально, следующий класс в классе Method Resolution Order или MRO) с текущим объектом self, а затем вызывает его и позволяет выполнять работа.

Все эти проблемы можно избежать, используя __getattr__, который позволяет Python делать это нормально, пока атрибут не будет найден. В этот момент Python контролирует ваш метод __getattr__ и позволяет ему что-то придумать.

Также стоит отметить, что вы можете запустить бесконечную рекурсию с помощью __getattr__.

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

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

Ответ 3

Я думаю, что другие ответы проделали большую работу по разъяснению разницы между __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__.