Je veux remplacer l'accès à une variable dans une classe, mais renvoyer toutes les autres normalement. Comment puis-je accomplir cela avec __getattribute__
?
J'ai essayé ce qui suit (qui devrait également illustrer ce que j'essaie de faire) mais j'obtiens une erreur de récursivité:
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
Vous obtenez une erreur de récursivité, car votre tentative d'accès à self.__dict__
attribut à l'intérieur __getattribute__
invoque votre __getattribute__
encore. Si vous utilisez object
's __getattribute__
à la place, cela fonctionne:
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)
Cela fonctionne parce que object
(dans cet exemple) est la classe de base. En appelant la version de base de __getattribute__
vous évitez l'enfer récursif dans lequel vous étiez auparavant.
Sortie Ipython avec du code dans 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
Mise à jour:
Il y a quelque chose dans la section intitulée Plus d'accès aux attributs pour les classes de nouveau style dans la documentation actuelle, où ils recommandent de faire exactement cela pour éviter la récursion infinie.
En fait, je pense que vous souhaitez plutôt utiliser la méthode spéciale __getattr__
.
Citation des documents Python:
__getattr__( self, name)
Appelé lorsqu'une recherche d'attribut n'a pas trouvé l'attribut aux endroits habituels (c'est-à-dire qu'il ne s'agit pas d'un attribut d'instance ni qu'il se trouve dans l'arborescence des classes pour lui-même). nom est le nom de l'attribut. Cette méthode doit renvoyer la valeur d'attribut (calculée) ou déclencher une exception AttributeError.
Notez que si l'attribut est trouvé via le mécanisme normal,__getattr__()
n'est pas appelé. (Il s'agit d'une asymétrie intentionnelle entre__getattr__()
et__setattr__()
.) Ceci est fait à la fois pour des raisons d'efficacité et parce qu'autrement__setattr__()
n'aurait aucun moyen d'accéder à d'autres attributs de l'instance. Notez qu'au moins pour les variables d'instance, vous pouvez simuler un contrôle total en n'insérant aucune valeur dans le dictionnaire d'attributs d'instance (mais plutôt en les insérant dans un autre objet). Voir la méthode__getattribute__()
ci-dessous pour un moyen d'obtenir un contrôle total dans les classes de nouveau style.
Remarque: pour que cela fonctionne, l'instance doit pas avoir un attribut test
, donc la ligne self.test=20
Doit être supprimée.
Afin d'éviter une récursion infinie dans cette méthode, son implémentation doit toujours appeler la méthode de classe de base avec le même nom pour accéder à tous les attributs dont elle a besoin, par exemple
object.__getattribute__(self, name)
.
Sens:
def __getattribute__(self,name):
...
return self.__dict__[name]
Vous appelez un attribut appelé __dict__
. Parce que c'est un attribut, __getattribute__
est appelé à la recherche de __dict__
qui appelle __getattribute__
qui appelle ... yada yada yada
return object.__getattribute__(self, name)
Utilisation des classes de base __getattribute__
aide à trouver le véritable attribut.
Voulez-vous vraiment utiliser __getattribute__
? Qu'essayez-vous réellement d'accomplir?
La façon la plus simple de faire ce que vous demandez est:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
test = 0
ou:
class D(object):
def __init__(self):
self.test = 20
self.test2 = 21
@property
def test(self):
return 0
Edit: Notez qu'une instance de D
aurait des valeurs différentes de test
dans chaque cas. Dans le premier cas d.test
serait 20, dans le second ce serait 0. Je vous laisse le soin de comprendre pourquoi.
Edit2: Greg a souligné que l'exemple 2 échouera car la propriété est en lecture seule et le __init__
La méthode a essayé de la mettre à 20. Un exemple plus complet serait:
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)
Évidemment, en tant que classe, cela est presque entièrement inutile, mais cela vous donne une idée pour passer à autre chose.
Voici une version plus fiable:
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)
Il appelle la méthode __ getattribute __ de la classe parent, pour finalement retomber sur l'objet .__ getattribute __ méthode si d'autres ancêtres ne la remplacent pas.
Comment est la
__getattribute__
méthode utilisée?
Il est appelé avant la recherche normale en pointillés. S'il soulève AttributeError
, alors nous appelons __getattr__
.
L'utilisation de cette méthode est plutôt rare. Il n'y a que deux définitions dans la bibliothèque standard:
$ grep -Erl "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py
Meilleures pratiques
La bonne façon de contrôler par programme l'accès à un seul attribut est avec property
. La classe D
doit être écrite comme suit (avec le setter et le deleter facultativement pour reproduire le comportement prévu apparent):
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'''
Et l'utilisation:
>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
Une propriété est un descripteur de données, c'est donc la première chose recherchée dans l'algorithme de recherche en pointillé normal.
__getattribute__
Vous avez plusieurs options si vous devez absolument implémenter la recherche de chaque attribut via __getattribute__
.
AttributeError
, provoquant __getattr__
à appeler (si implémenté)super
pour appeler l'implémentation parent (probablement object
)__getattr__
Par exemple:
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
Et cet exemple montre comment vous pourriez faire ce que vous vouliez à l'origine:
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)
Et se comportera comme ceci:
>>> 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
Votre code avec commentaires. Vous avez une recherche en pointillé sur vous-même dans __getattribute__
. C'est pourquoi vous obtenez une erreur de récursivité. Vous pouvez vérifier si le nom est "__dict__"
et utilisez super
pour contourner ce problème, mais cela ne couvre pas __slots__
. Je laisse cela comme un exercice au lecteur.
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