Я хочу переопределить доступ к одной переменной в классе, но вернуть все остальные в обычном режиме. Как мне добиться этого с помощью __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'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
Обновлять:
Есть кое-что в разделе под названием Больше атрибутов доступа для классов нового стиля в текущей документации, где они рекомендуют делать именно это, чтобы избежать бесконечной рекурсии.
..и я хочу всегда использовать объект? Что, если я унаследую от других классов?
Потому что это родительский класс. Вы говорите с версией __getattribute __ () базового класса, а не с вашим новым блестящим (с добавлением рекурсии) __getattribute __ ()
Используйте класс, от которого вы наследуете. Что бы это ни было.
Если вы посмотрите на первый аргумент функции объекта, он будет self. Self - это текущий экземпляр вашего класса, объекта. Вы используете его аналогичным образом, когда вы подклассифицируете другие классы, и вам нужно вызвать их конструкцию, в этом
Итак, я беру только функцию getattribute из базового класса, значения по-прежнему исходят из моего класса. Так действительно ли вы можете использовать любой класс по иерархии?
Хотя я предполагаю, что у объекта будет заведомо исправный getattribute?
Да, каждый раз, когда вы создаете класс и не пишете свой собственный, вы используете getattribute, предоставленный объектом.
Эгиль: Это не совсем безопасный способ смотреть на это. Если я использую класс, наследующий другой класс, наследующий другой класс, наследующий объект (и я не писал ни одного из унаследованных классов). Если один из этих классов переопределяет getattribute и вы вызываете версию объекта, он может не работать как. ..
... ожидал. Вы всегда должны вызывать следующую ступень лестницы наследования (если вы не знаете, что делаете).
Но я вижу, что мой комментарий может ввести в заблуждение.
@Oli, @Egil, я не понимаю, зачем `передавать 2 аргумента в объект getattr вот так, object .__ getattribute __ (self, name)? getattr - это не статический метод, верно?
@Alcott: Нет, __getattribute__() не является статическим методом, именно поэтому при вызове несвязанной версии класса object необходимо передать аргумент экземпляра self в дополнение к атрибуту name.
не лучше ли использовать super () и, таким образом, использовать первый метод getattribute, который python находит в ваших базовых классах? - super(D, self).__getattribute__(name)
Или вы можете просто использовать super().__getattribute__(name) в Python 3.
Вы уверены, что хотите использовать __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: невозможно установить атрибут
Правда. Я исправлю это как третий пример. Хорошо подмечено.
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 было бы бесполезным, поскольку он всегда находил бы его «в обычных местах».
текущая ссылка на соответствующие документы Python (похоже, отличается от указанной в ответе): docs.python.org/3/reference/datamodel.html#object.__getattr_ _
Вот более надежная версия:
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__ (если реализовано)Например:
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
Интересно. Так что ты там делаешь? Зачем возражать мои переменные?