Como o __getattribute__
método é usado?
É chamado antes da pesquisa pontilhada normal. Se aumentar AttributeError
, então pagamos __getattr__
.
O uso deste método é bastante raro. Existem apenas duas definições na biblioteca padrão:
$ grep -Erl "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
Melhor prática
A maneira correta de controlar programaticamente o acesso a um único atributo é com property
. A classe D
deve ser escrita da seguinte forma (com o setter e o deleter opcionalmente para replicar o comportamento pretendido aparente):
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'''
E uso:
>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
Uma propriedade é um descritor de dados, portanto, é a primeira coisa procurada no algoritmo normal de pesquisa pontilhada.
Opções para __getattribute__
Você terá várias opções se for absolutamente necessário implementar a pesquisa para cada atributo via __getattribute__
.
- aumentar
AttributeError
, fazendo __getattr__
com que seja chamado (se implementado)
- devolver algo dele por
- usando
super
para chamar a object
implementação pai (provavelmente )
- chamando
__getattr__
- implementar seu próprio algoritmo de pesquisa pontilhada de alguma forma
Por exemplo:
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
O que você queria originalmente.
E este exemplo mostra como você pode fazer o que queria originalmente:
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)
E vai se comportar assim:
>>> 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
Revisão de código
Seu código com comentários. Você tem uma pesquisa pontilhada sobre si mesmo em __getattribute__
. É por isso que você obtém um erro de recursão. Você pode verificar se o nome é "__dict__"
e usar super
para contornar o problema, mas isso não cobre __slots__
. Vou deixar isso como um exercício para o leitor.
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