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

Я хочу переопределить доступ к одной переменной в classе, но возвращать все остальные обычно. Как это сделать с помощью __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 

Вы получаете ошибку рекурсии, потому что ваша попытка получить доступ к self.__dict__ внутри __getattribute__ вызывает ваш __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 (в этом примере) является базовым classом. __getattribute__ базовую версию __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 

Обновить:

В разделе « Дополнительные атрибуты» для classов нового стиля в текущей документации есть что-то в разделе, где они рекомендуют делать именно это, чтобы избежать бесконечной рекурсии.

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

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

__getattr__( self, name)

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

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

Ссылка на язык Python:

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

Имея в виду:

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

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

 return object.__getattribute__(self, name) 

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

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

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

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

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

Как __getattribute__ метод __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 "", line 1, in  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 
  • Что именно делает метод .join ()?
  • Разбор строк datetime, содержащих наносекунды
  • Как обновить параметры модели с накопленными gradleиентами?
  • В виде флага отображается ошибка 400 вместо шаблона с формой
  • Pandas groupby diff
  • Почему код Python использует функцию len () вместо метода длины?
  • Как установить тайм-аут на метод recv сокета python?
  • Заменить значение для выбранной ячейки в pandas DataFrame без использования индекса
  • Вставка имени таблицы в запрос дает sqlite3.OperationalError: рядом с «?»: Синтаксическая ошибка
  • Цитирование обратных косых черт в строковых литералах Python
  • Являются ли словари упорядоченными в Python 3.6+?
  • Давайте будем гением компьютера.