Python: избегать бесконечных циклов в __getattribute__
Метод __getattribute__
должен быть написан тщательно, чтобы избежать бесконечного цикла. Например:
class A:
def __init__(self):
self.x = 100
def __getattribute__(self, x):
return self.x
>>> a = A()
>>> a.x # infinite looop
RuntimeError: maximum recursion depth exceeded while calling a Python object
class B:
def __init__(self):
self.x = 100
def __getattribute__(self, x):
return self.__dict__[x]
>>> b = B()
>>> b.x # infinite looop
RuntimeError: maximum recursion depth exceeded while calling a Python object
Следовательно, нам нужно написать метод таким образом:
class C:
def __init__(self):
self.x = 100
def __getattribute__(self, x):
# 1. error
# AttributeError: type object 'object' has no attribute '__getattr__'
# return object.__getattr__(self, x)
# 2. works
return object.__getattribute__(self, x)
# 3. works too
# return super().__getattribute__(x)
Мой вопрос: почему работает метод object.__getattribute__
? Откуда object
получает метод __getattribute__
? И если object
не имеет __getattribute__
, тогда мы просто вызываем тот же метод в классе C
, но через суперкласс. Почему, тогда вызов метода через суперкласс не приводит к бесконечному циклу?
Ответы
Ответ 1
Кажется, у вас __getattribute__
впечатление, что ваша реализация __getattribute__
- это просто крючок, который, если вы предоставите его, назовет его Python, и в противном случае интерпретатор будет делать свою обычную магию напрямую.
Это неверно. Когда python ищет атрибуты экземпляров, __getattribute__
является основной записью для доступа к атрибутам, а object
предоставляет реализацию по умолчанию. Таким образом, ваша реализация переопределяет оригинал, и если ваша реализация не предоставляет альтернативных способов возврата атрибутов, она не выполняется. Вы не можете использовать доступ к атрибуту в этом методе, поскольку все атрибуты доступа к экземпляру (self
) снова type(self).__getattribute__(self, attr)
через type(self).__getattribute__(self, attr)
.
Самый лучший способ - снова вызвать переопределенный оригинал. То, что происходит super(C, self).__getattribute__(attr)
; вы запрашиваете следующий класс в порядке разрешения класса, чтобы заботиться о доступе к атрибуту для вас.
Кроме того, вы можете напрямую object.__getattribute__()
метод object.__getattribute__()
. Реализация C этого метода является окончательной остановкой доступа к атрибуту (он имеет прямой доступ к __dict__
и, следовательно, не связан с теми же ограничениями).
Обратите внимание: super()
возвращает прокси-объект, который будет искать, какой метод можно найти далее в базовых классах, упорядоченных по методу. Если такой метод не существует, он будет терпеть неудачу с ошибкой атрибута. Он никогда не вызовет оригинальный метод. Таким образом, Foo.bar()
ищет super(Foo, self).bar
будет либо реализацией базового класса, либо ошибкой атрибута, никогда не самой Foo.bar
.
Ответ 2
Когда вы это сделаете:
return object.__getattribute__(self, x)
вы вызываете определенную функцию - ту, которая определена в классе объекта, а не та, которая определена в A, поэтому нет рекурсии.
Когда вы это сделаете:
return self.x
вы позволяете python выбирать, какую функцию вызывать, и он вызывает один из A, и вы имеете бесконечную рекурсию.
Ответ 3
Запишите его так (C наследует от объекта):
class C(object):
def __init__(self):
self.x = 100
def __getattribute__(self, x):
return object.__getattribute__(self, x)
Теперь вы видите, почему работает объект.__ getattribute __ (self, x) - вы вызываете родительский объект.