Понимание разницы между __getattr__ и __getattribute__ - PullRequest
184 голосов
/ 28 ноября 2010

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

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

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

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

Затем говорится:

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

Почему?

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

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

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

Ответы [ 3 ]

270 голосов
/ 28 ноября 2010

Сначала немного основ.

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

85 голосов
/ 28 ноября 2010

__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

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

58 голосов
/ 28 ноября 2010

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

...