it-swarm.com.ru

Как реализовать __getattribute__ без бесконечной ошибки рекурсии?

Я хочу переопределить доступ к одной переменной в классе, но все остальные вернуть нормально. Как мне сделать это с __getattribute__?

Я попробовал следующее (что также должно иллюстрировать то, что я пытаюсь сделать), но я получаю ошибку рекурсии:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
80
Greg

Вы получаете ошибку рекурсии, потому что ваша попытка получить доступ к атрибуту self.__dict__ внутри __getattribute__ снова вызывает ваш __getattribute__. Если вместо этого вы используете object's __getattribute__, это работает:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return object.__getattribute__(self, name)

Это работает, потому что object (в этом примере) является базовым классом. Вызывая базовую версию __getattribute__, вы избегаете рекурсивного ада, в котором вы были раньше.

Вывод Ipython с кодом в foo.py:

In [1]: from foo import *

In [2]: d = D()

In [3]: d.test
Out[3]: 0.0

In [4]: d.test2
Out[4]: 21

Update:

Что-то в разделе под названием Больше доступа к атрибутам для классов нового стиля в текущей документации, где они рекомендуют делать именно это, чтобы избежать бесконечной рекурсии.

108
Egil

На самом деле, я считаю, что вы хотите использовать вместо этого специальный метод __getattr__.

Цитата из Python документации:

__getattr__( self, name)

Вызывается, когда поиск атрибута не нашел атрибут в обычных местах (т.е. он не является атрибутом экземпляра и не найден в дереве классов для себя). имя это имя атрибута. Этот метод должен возвращать (вычисленное) значение атрибута или вызывать исключение AttributeError.
Обратите внимание, что если атрибут найден с помощью обычного механизма, функция __getattr__() не вызывается. (Это преднамеренная асимметрия между __getattr__() и __setattr__().) Это делается как по соображениям эффективности, так и в противном случае __setattr__() не будет иметь доступа к другим атрибутам экземпляра. Обратите внимание, что по крайней мере для переменных экземпляра вы можете подделать тотальный контроль, не вставляя никаких значений в словарь атрибутов экземпляра (вместо этого вставляя их в другой объект). Смотрите ниже метод __getattribute__(), чтобы узнать, как на самом деле получить полный контроль в классах нового стиля.

Примечание: чтобы это работало, экземпляр должен не иметь атрибут test, поэтому строку self.test=20 следует удалить.

22
tzot

Справочник по языку Python:

Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым необходимым атрибутам, например, object.__getattribute__(self, name).

Имея в виду:

def __getattribute__(self,name):
    ...
        return self.__dict__[name]

Вы вызываете атрибут с именем __dict__. Поскольку это атрибут, __getattribute__ вызывается при поиске __dict__, который вызывает __getattribute__, который вызывает ... yada yada yada

return  object.__getattribute__(self, name)

Использование базовых классов __getattribute__ помогает найти настоящий атрибут.

15
ttepasse

Вы уверены, что хотите использовать __getattribute__? Чего вы на самом деле пытаетесь достичь?

Самый простой способ сделать то, что вы просите, это:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    test = 0

или же:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    @property
    def test(self):
        return 0

Правка: Обратите внимание, что экземпляр D будет иметь разные значения test в каждом случае. В первом случае d.test будет 20, во втором - 0. Я оставлю это на ваше усмотрение, почему.

Edit2: Грег указал, что пример 2 потерпит неудачу, потому что свойство доступно только для чтения, а метод __init__ попытался установить его в 20. Более полный пример для этого будет:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    _test = 0

    def get_test(self):
        return self._test

    def set_test(self, value):
        self._test = value

    test = property(get_test, set_test)

Очевидно, что в классе это почти совершенно бесполезно, но дает вам идею двигаться дальше.

13
Singletoned

Вот более надежная версия:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21
    def __getattribute__(self, name):
        if name == 'test':
            return 0.
        else:
            return super(D, self).__getattribute__(name)

Он вызывает _ GETATTRIBUTE _ метод из родительского класса, в конце концов возвращаясь к объекту ._ GETATTRIBUTE _ метод, если другие предки не переопределяют его.

5
ElmoVanKielmo

Как используется метод __getattribute__?

Он вызывается перед обычным точечным поиском. Если он вызывает AttributeError, тогда мы вызываем __getattr__.

Использование этого метода довольно редко. В стандартной библиотеке только два определения:

$ grep -Erl  "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py

Лучшая практика

Правильный способ программного управления доступом к одному атрибуту - property . Класс D должен быть записан следующим образом (с установщиком и удалителем, опционально, для воспроизведения предполагаемого предполагаемого поведения):

class D(object):
    def __init__(self):
        self.test2=21

    @property
    def test(self):
        return 0.

    @test.setter
    def test(self, value):
        '''dummy function to avoid AttributeError on setting property'''

    @test.deleter
    def test(self):
        '''dummy function to avoid AttributeError on deleting property'''

И использование:

>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0

Свойство - это дескриптор данных, поэтому это первое, что ищется в обычном алгоритме точечного поиска.

Опции для __getattribute__

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

  • поднять AttributeError, вызывая вызов __getattr__ (если реализовано)
  • вернуть что-нибудь из этого
    • используя super для вызова родительской (возможно, object) реализации
    • вызов __getattr__
    • реализовать свой собственный алгоритм точечного поиска как-то

Например:

class NoisyAttributes(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self, name):
        print('getting: ' + name)
        try:
            return super(NoisyAttributes, self).__getattribute__(name)
        except AttributeError:
            print('oh no, AttributeError caught and reraising')
            raise
    def __getattr__(self, name):
        """Called if __getattribute__ raises AttributeError"""
        return 'close but no ' + name    


>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
'close but no foo'
>>> n.test
getting: test
20

То, что вы изначально хотели.

И этот пример показывает, как вы можете сделать то, что вы изначально хотели:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return super(D, self).__getattribute__(name)

И будет вести себя так:

>>> o = D()
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test

Traceback (most recent call last):
  File "<pyshell#216>", line 1, in <module>
    del o.test
AttributeError: test

Обзор кода

Ваш код с комментариями. У вас есть точечный поиск себя в __getattribute__. Вот почему вы получаете ошибку рекурсии. Вы можете проверить, является ли name "__dict__", и использовать super для обхода, но это не распространяется на __slots__. Я оставлю это в качестве упражнения для читателя.

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:      #   v--- Dotted lookup on self in __getattribute__
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp
3
Aaron Hall