Подтвердить что ты не робот

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, но через суперкласс. Почему, тогда вызов метода через суперкласс не приводит к бесконечному циклу?

4b9b3361

Ответ 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) - вы вызываете родительский объект.