Как используется метод __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

Ответ 1

Вы получаете ошибку рекурсии, потому что вы вызываете ту же функцию, ваш __getattribute__. Если вместо этого вы используете object __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:

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

Ответ 2

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

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

__getattr__( self, name)

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

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

Ответ 3

Ссылка на язык 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__ помогает найти истинный атрибут.

Ответ 4

Вы действительно хотите использовать __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)

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

Ответ 5

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

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 __, если другие предки не переопределяют его.

Ответ 6

Как используется метод __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__.

  • raise 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__. Вот почему вы получаете ошибку рекурсии. Вы можете проверить, есть ли имя "__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