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