Quel est l'attribut __dict __.__ dict__ d'une classe Python?

91
>>> class A(object): pass
... 
>>> A.__dict__
<dictproxy object at 0x173ef30>
>>> A.__dict__.__dict__
Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
AttributeError: 'dictproxy' object has no attribute '__dict__'
>>> A.__dict__.copy()
{'__dict__': <attribute '__dict__' of 'A' objects> ... }
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects> # What is this object?

Si je le fais A.something = 10, cela entre A.__dict__. Quel est ce <attribute '__dict__' of 'A' objects>trouve dans A.__dict__.__dict__, et quand elle contient quelque chose?

porgarmingduod
la source
11
Un exemple de variable plus approprié aurait été ive. Au moins, cela aurait fait une autre A.__dict__['ive']question;) Je me verrai dehors
Joakim

Réponses:

109

Tout d'abord A.__dict__.__dict__est différent de A.__dict__['__dict__'], et le premier n'existe pas. Ce dernier est l' __dict__attribut qu'auraient les instances de la classe. C'est un objet descripteur qui renvoie le dictionnaire interne des attributs pour l'instance spécifique. En bref, l' __dict__attribut d'un objet ne peut pas être stocké dans un objet __dict__, il est donc accessible via un descripteur défini dans la classe.

Pour comprendre cela, vous devez lire la documentation du protocole de descripteur .

La version courte:

  1. Pour une instance de classe A, l'accès à instance.__dict__est fourni par A.__dict__['__dict__']lequel est identique à vars(A)['__dict__'].
  2. Pour la classe A, l'accès à A.__dict__est fourni par type.__dict__['__dict__'](en théorie) ce qui est identique à vars(type)['__dict__'].

La version longue:

Les classes et les objets donnent accès aux attributs à la fois via l'opérateur d'attribut (implémenté via la classe ou la métaclasse __getattribute__) et l' __dict__attribut / protocole utilisé par vars(ob).

Pour les objets normaux, l' __dict__objet crée un dictobjet séparé , qui stocke les attributs, et __getattribute__essaie d'abord d'y accéder et d'obtenir les attributs à partir de là (avant d'essayer de rechercher l'attribut dans la classe en utilisant le protocole descripteur, et avant d'appeler __getattr__). Le __dict__descripteur de la classe implémente l'accès à ce dictionnaire.

  • x.nameest équivalent à ceux pour essayer: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ fait de même mais saute le premier pour des raisons évidentes

Comme il est impossible que le __dict__of instancesoit stocké dans __dict__l'instance, il est directement accessible via le protocole de descripteur et est stocké dans un champ spécial de l'instance.

Un scénario similaire est vrai pour les classes, bien que leur __dict__ un objet proxy spécial qui prétend être un dictionnaire (mais peut ne pas l'être en interne), et ne vous permet pas de le modifier ou de le remplacer par un autre. Ce proxy vous permet, entre autres, d'accéder aux attributs d'une classe qui lui sont spécifiques, et non définis dans l'une de ses bases.

Par défaut, une vars(cls)classe vide porte trois descripteurs - __dict__pour stocker les attributs des instances, __weakref__qui sont utilisés en interne par weakref, et la docstring de la classe. Les deux premiers pourraient avoir disparu si vous définissez __slots__. Vous n'auriez alors pas d' attributs __dict__et __weakref__, mais à la place, vous auriez un attribut de classe unique pour chaque emplacement. Les attributs de l'instance ne seraient alors pas stockés dans un dictionnaire, et leur accès sera fourni par les descripteurs respectifs de la classe.


Et enfin, l'incohérence qui A.__dict__est différente de A.__dict__['__dict__']c'est parce que l'attribut __dict__, par exception, n'est jamais recherché vars(A), donc ce qui est vrai car ce n'est pas vrai pour pratiquement tout autre attribut que vous utiliseriez. Par exemple, A.__weakref__c'est la même chose que A.__dict__['__weakref__']. Si cette incohérence n'existait pas, l'utilisation A.__dict__ne fonctionnerait pas et vous devriez toujours utiliser à la vars(A)place.

Rosh Oxymoron
la source
6
Merci pour la réponse détaillée. Bien que j'aie dû le lire plusieurs fois, je pense que cela fait un moment que j'ai appris tant de nouveaux détails sur Python.
porgarmingduod
Pourquoi exactement l' __dict__attribut d'un objet ne peut-il pas être stocké dans celui de l'objet __dict__?
zumgruenenbaum
2
@zumgruenenbaum Puisque __dict__est censé stocker tous les attributs d'instance, un accès aux attributs du formulaire obj.xest finalement recherché sur l'objet __dict__, à savoir obj.__dict__['x']. Maintenant, s'il __dict__n'était pas implémenté en tant que descripteur, cela conduirait à une récursivité infinie, car pour y accéder, obj.__dict__vous devez le rechercher sous la forme obj.__dict__['__dict__']. Le descripteur contourne ce problème.
a_guest le
10

Puisqu'il A.__dict__s'agit d'un dictionnaire stockant des Aattributs, A.__dict__['__dict__']est la référence directe à ce même A.__dict__attribut.

A.__dict__contient une (sorte de) référence à lui-même. La partie "kind-of" est la raison pour laquelle l'expression A.__dict__renvoie a dictproxyau lieu d'une normale dict.

>>> class B(object):
...     "Documentation of B class"
...     pass
...
>>> B.__doc__
'Documentation of B class'
>>> B.__dict__
<dictproxy object at 0x00B83590>
>>> B.__dict__['__doc__']
'Documentation of B class'
vz0
la source
9
A.__dict__['__dict__']n'est pas une référence à A.__dict__. Il implémente l' __dict__attribut des instances. Pour essayer cela par vous-même, A.__dict__['__dict__'].__get__(A(), A)renvoie les attributs de A(), while A.__dict__['__dict__'].__get__(A, type)échoue.
Rosh Oxymoron
10

Faisons quelques explorations!

>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>

Je me demande ce que c'est?

>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>

Quels sont les attributs d'un getset_descriptorobjet?

>>> type(A.__dict__["__dict__"]).__dict__
<dictproxy object at 0xb7efc4ac>

En faisant une copie de cela, dictproxynous pouvons trouver des attributs intéressants, en particulier __objclass__et __name__.

>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__
(<class '__main__.A'>, '__dict__')

Donc , __objclass__est une référence à Aet __name__est juste la chaîne '__dict__', le nom d'un attribut peut-être?

>>> getattr(A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) == A.__dict__
True

Voilà, nous l'avons! A.__dict__['__dict__']est un objet auquel on peut renvoyer A.__dict__.

Andrew Clark
la source
La PEP 252 dit que __objclass__c'est la classe qui a défini cet attribut, et non un attribut de cette classe. Cela rend votre getattrexemple incorrect. Un plus correct seraitgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron
1
@RoshOxymoron Votre expression augmente KeyError: '__dict__', contrairement à celle de @ AndrewClark.
Maggyero
9

Vous pouvez essayer l'exemple simple suivant pour en savoir plus:

>>> class A(object): pass
... 
>>> a = A()
>>> type(A)
<type 'type'>
>>> type(a)
<class '__main__.A'>
>>> type(a.__dict__)
<type 'dict'>
>>> type(A.__dict__)
<type 'dictproxy'>
>>> type(type.__dict__)
<type 'dictproxy'>
>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> type(type.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> a.__dict__ == A.__dict__['__dict__'].__get__(a)
True
>>> A.__dict__ == type.__dict__['__dict__'].__get__(A)
True
>>> a.__dict__ == type.__dict__['__dict__'].__get__(A)['__dict__'].__get__(a)
True

D'après l'exemple ci-dessus, il semble que les attributs des objets de classe sont stockés par leur classe, les attributs de classe sont stockés par leur classe, qui sont des métaclasses. Ceci est également validé par:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
damaZhang
la source
1
Curieusement, si isest remplacé ==dans la deuxième comparaison, c'est A.__dict__ is type.__dict__['__dict__'].__get__(A)-à- dire que le résultat est Falseà la fois en python 2.7.15+ et 3.6.8.
Arne Vogel