Как реализовать __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
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
108
0
101 764
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Вы получаете ошибку рекурсии, потому что ваша попытка получить доступ к атрибуту 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

Обновлять:

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

Интересно. Так что ты там делаешь? Зачем возражать мои переменные?

Greg 16.12.2008 19:27

..и я хочу всегда использовать объект? Что, если я унаследую от других классов?

Greg 16.12.2008 19:29

Потому что это родительский класс. Вы говорите с версией __getattribute __ () базового класса, а не с вашим новым блестящим (с добавлением рекурсии) __getattribute __ ()

Oli 16.12.2008 19:29

Используйте класс, от которого вы наследуете. Что бы это ни было.

Oli 16.12.2008 19:30

Если вы посмотрите на первый аргумент функции объекта, он будет self. Self - это текущий экземпляр вашего класса, объекта. Вы используете его аналогичным образом, когда вы подклассифицируете другие классы, и вам нужно вызвать их конструкцию, в этом

Egil 16.12.2008 19:31

Итак, я беру только функцию getattribute из базового класса, значения по-прежнему исходят из моего класса. Так действительно ли вы можете использовать любой класс по иерархии?

Greg 16.12.2008 19:31

Хотя я предполагаю, что у объекта будет заведомо исправный getattribute?

Greg 16.12.2008 19:32

Да, каждый раз, когда вы создаете класс и не пишете свой собственный, вы используете getattribute, предоставленный объектом.

Egil 16.12.2008 19:33

Эгиль: Это не совсем безопасный способ смотреть на это. Если я использую класс, наследующий другой класс, наследующий другой класс, наследующий объект (и я не писал ни одного из унаследованных классов). Если один из этих классов переопределяет getattribute и вы вызываете версию объекта, он может не работать как. ..

Oli 16.12.2008 19:37

... ожидал. Вы всегда должны вызывать следующую ступень лестницы наследования (если вы не знаете, что делаете).

Oli 16.12.2008 19:38

Но я вижу, что мой комментарий может ввести в заблуждение.

Egil 16.12.2008 19:45

@Oli, @Egil, я не понимаю, зачем `передавать 2 аргумента в объект getattr вот так, object .__ getattribute __ (self, name)? getattr - это не статический метод, верно?

Alcott 11.10.2011 05:28

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

martineau 18.07.2012 21:54

не лучше ли использовать super () и, таким образом, использовать первый метод getattribute, который python находит в ваших базовых классах? - super(D, self).__getattribute__(name)

gepatino 07.08.2012 17:05

Или вы можете просто использовать super().__getattribute__(name) в Python 3.

jeromej 12.11.2013 17:01

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

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

О, это не тихая работа, когда ты ведешь класс, не так ли? Файл "Script1.py", строка 5, в в этом self.test = 20 AttributeError: невозможно установить атрибут

Greg 16.12.2008 20:06

Правда. Я исправлю это как третий пример. Хорошо подмечено.

Singletoned 17.12.2008 02:14

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

In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, 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__ помогает найти настоящий атрибут.

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

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

__getattr__( self, name)

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.
Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __setattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control in new-style classes.

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

Фактически, в соответствии с природой кода OP, переопределение __getattr__ для test было бы бесполезным, поскольку он всегда находил бы его «в обычных местах».

Casey Kuball 01.03.2012 00:53

текущая ссылка на соответствующие документы Python (похоже, отличается от указанной в ответе): docs.python.org/3/reference/datamodel.html#object.__getattr_‌ _

CrepeGoat 16.04.2020 22:41

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

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__ из родительского класса, в конечном итоге возвращаясь к методу object.__getattribute__, если другие предки не отменяют его.

How is the __getattribute__ method used?

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

Другие вопросы по теме