Как реализовать __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
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
Обновить:
В текущей документации есть что-то в разделе " Доступ к атрибутам More" для классов нового стиля, где они рекомендуют делать именно это, чтобы избежать бесконечной рекурсии.
На самом деле, я считаю, что вы хотите использовать __getattr__
специальный метод вместо.
Цитата из документации по Python:
__getattr__( self, name)
Вызывается, когда поиск атрибута не нашел атрибут в обычных местах (т. Е. Он не является атрибутом экземпляра и не найден в дереве классов для себя). имя это имя атрибута. Этот метод должен возвращать (вычисленное) значение атрибута или вызывать исключение AttributeError.
Обратите внимание, что если атрибут найден через обычный механизм,__getattr__()
не называется. (Это намеренная асимметрия между__getattr__()
а также__setattr__()
.) Это сделано как по соображениям эффективности, так и потому, что в противном случае__setattr__()
не будет иметь доступа к другим атрибутам экземпляра. Обратите внимание, что по крайней мере для переменных экземпляра вы можете подделать тотальный контроль, не вставляя никаких значений в словарь атрибутов экземпляра (но вместо этого вставляя их в другой объект). Увидеть__getattribute__()
метод ниже для способа фактически получить полный контроль в классах нового стиля.
Примечание: чтобы это работало, экземпляр не должен иметь test
атрибут, поэтому линия self.test=20
должны быть удалены.
Чтобы избежать бесконечной рекурсии в этом методе, его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым атрибутам, которые ему нужны, например,
object.__getattribute__(self, name)
,
Имея в виду:
def __getattribute__(self,name):
...
return self.__dict__[name]
Вы вызываете атрибут с именем __dict__
, Потому что это атрибут, __getattribute__
вызывается в поисках __dict__
какие звонки __getattribute__
который называет... Яда Яда Яда
return object.__getattribute__(self, name)
Использование базовых классов __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 "<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
Вы уверены, что хотите использовать __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)
Очевидно, что для класса это почти совершенно бесполезно, но дает вам идею двигаться дальше.
Вот более надежная версия:
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__ метод, если другие предки не переопределяют его.