104 votes

Qu'est-ce que l'attribut __dict__.__dict__ d'une classe Python ?

>>> 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 va dans A.__dict__ . Ce que est ce <attribute '__dict__' of 'A' objects> trouvé dans A.__dict__.__dict__ et quand contient-il quelque chose ?

13 votes

Un exemple de variable plus approprié aurait été ive . Au moins, ça aurait fait de ce film un plus A.__dict__['ive'] question ;) Je vais m'en sortir

114voto

Rosh Oxymoron Points 6965

Tout d'abord A.__dict__.__dict__ est différent de A.__dict__['__dict__'] . Le premier n'existe pas et le second est le __dict__ que les instances de la classe auront. C'est un objet descripteur de données qui renvoie le dictionnaire interne des attributs pour l'instance spécifique. En bref, l'objet __dict__ d'un objet ne peut pas être stocké dans l'objet __dict__ On y accède donc par un descripteur défini dans la classe.

Pour comprendre cela, il faut lire le document suivant documentation du protocole des descripteurs .

La version courte :

  1. Pour une instance a d'une classe A l'accès à a.__dict__ est fourni par A.__dict__['__dict__'] qui est identique à vars(A)['__dict__'] .
  2. Pour une 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 permettent d'accéder aux attributs par l'intermédiaire de l'opérateur d'attributs (mis en œuvre via l'opérateur d'attributs de la classe ou de la métaclasse). __getattribute__ ), et le __dict__ attribut/protocole qui est utilisé par vars(ob) .

Pour les objets normaux, le __dict__ crée un objet séparé dict qui stocke les attributs, et __getattribute__ essaie d'abord d'y accéder et de récupérer les attributs à partir de là (avant d'essayer de rechercher l'attribut dans la classe en utilisant le protocole de descripteur, et avant d'appeler le fichier __getattr__ ). Le site __dict__ Le descripteur de la classe implémente l'accès à ce dictionnaire.

  • a.name est équivalent à essayer ceux dans l'ordre : type(a).__dict__['name'].__get__(a, type(a)) (uniquement si type(a).__dict__['name'] es un données descripteur), a.__dict__['name'] , type(a).__dict__['name'].__get__(a, type(a)) , type(a).__dict__['name'] .
  • a.__dict__ fait la même chose mais saute la deuxième étape pour des raisons évidentes.

Comme il est impossible pour le __dict__ d'une instance pour être stockée en elle-même, on y accède directement par le protocole des descripteurs et elle est stockée dans un champ spécial de l'instance.

Un scénario similaire est vrai pour les classes, bien que leur __dict__ est un objet proxy spécial qui prétend être un dictionnaire (mais qui peut ne pas l'être en interne), et qui 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 qui ne sont pas définis dans l'une de ses bases.

Par défaut, un vars(cls) d'une classe vide porte trois descripteurs : __dict__ pour stocker les attributs des instances, __weakref__ qui est utilisé en interne par weakref y __doc__ la docstring de la classe. Les deux premiers peuvent disparaître si vous définissez __slots__ . Alors vous n'auriez pas __dict__ y __weakref__ mais à la place, il y aurait un seul attribut de classe pour chaque slot. Les attributs de l'instance ne seraient alors pas stockés dans un dictionnaire, et l'accès à ceux-ci serait fourni par les descripteurs respectifs dans la classe.


Et enfin, l'incohérence que A.__dict__ est différent de A.__dict__['__dict__'] c'est parce que l'attribut __dict__ est, par exception, jamais regardé en haut vars(A) Ce qui est vrai pour lui ne l'est pas pour pratiquement tous les autres attributs que vous pourriez utiliser. Par exemple, A.__weakref__ est la même chose que A.__dict__['__weakref__'] . Si cette incohérence n'existait pas, en utilisant A.__dict__ ne fonctionnerait pas, et vous devriez toujours utiliser vars(A) à la place.

7 votes

Merci pour cette réponse détaillée. Bien que j'aie dû la lire plusieurs fois, je pense que cela faisait longtemps que je n'avais pas appris autant de nouveaux détails sur Python.

2 votes

Pourquoi exactement le __dict__ d'un objet est stocké dans l'attribut __dict__ ?

3 votes

@zumgruenenbaum Depuis __dict__ est destiné à stocker tous les attributs d'instance, un accès aux attributs de la forme obj.x est éventuellement recherchée dans la base de données de l'objet __dict__ à savoir obj.__dict__['x'] . Maintenant si __dict__ n'est pas implémenté comme un descripteur, cela conduirait à une récursion infinie, puisque pour accéder à obj.__dict__ vous devez le rechercher comme obj.__dict__['__dict__'] . Le descripteur permet de contourner ce problème.

11voto

damaZhang Points 151

Vous pouvez essayer l'exemple simple suivant pour mieux comprendre :

>>> 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 d'instance soient stockés par leur classe, et que les attributs de classe soient stockés par leur métaclasse. Ceci est également validé par :

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True

3 votes

Bizarrement, si is est substitué à == dans la deuxième comparaison, c'est-à-dire A.__dict__ is type.__dict__['__dict__'].__get__(A) le résultat est False dans les versions 2.7.15+ et 3.6.8 de python.

3 votes

@ArneVogel C'est parce que l'expression A.__dict__ (ou type.__dict__['__dict__'].__get__(A) ) est évalué à un nouveau types.MappingProxyType instance : A.__dict__ is not A.__dict__ . (Plus d'infos aquí sur l'histoire de ce type). Contrairement à l'expression a.__dict__ (ou A.__dict__['__dict__'].__get__(a) ) qui donne le même résultat dict instance : a.__dict__ is a.__dict__ .

1 votes

@Maggyero Je vois merci !

10voto

vz0 Points 11605

Depuis A.__dict__ est un dictionnaire stockant A attributs, 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 un dictproxy au lieu d'un 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'

10voto

Andrew Clark Points 77748

Allons explorer !

>>> 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_descriptor l'objet a ?

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

En faisant une copie de cette dictproxy nous pouvons trouver quelques attributs intéressants, notamment __objclass__ y __name__ .

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

Alors __objclass__ est une référence à A y __name__ est juste la chaîne de caractères '__dict__' le nom d'un attribut peut-être ?

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

Voilà, c'est fait ! A.__dict__['__dict__'] est un objet qui peut faire référence à A.__dict__ .

0 votes

La PEP 252 stipule que __objclass__ est la classe qui défini cet attribut, et non pas qui est un attribut de cette classe. Cela rend votre getattr exemple incorrect. Un exemple plus correct serait getattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)

1 votes

@RoshOxymoron Votre expression soulève KeyError: '__dict__' contrairement à celle de @AndrewClark.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X