Понимание разницы между __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__
.