¿Cuál es el atributo __dict __.__ dict__ de una clase de Python?

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

Si hago A.something = 10 , esto va en A.__dict__ . ¿Qué es este encuentra en A.__dict__.__dict__ , y cuándo contiene algo?

En primer lugar, A.__dict__.__dict__ es diferente de A.__dict__['__dict__'] , y el primero no existe. El último es el atributo __dict__ que __dict__ las instancias de la clase. Es un objeto descriptor que devuelve el diccionario interno de atributos para la instancia específica. En resumen, el atributo __dict__ de un objeto no se puede almacenar en el __dict__ del objeto, por lo que se accede a él a través de un descriptor definido en la clase.

Para entender esto, tendrías que leer la documentación del protocolo descriptor .

La versión corta:

  1. Para una instancia de clase A , el acceso a la instance.__dict__ es proporcionado por A.__dict__['__dict__'] que es lo mismo que vars(A)['__dict__'] .
  2. Para la clase A, el acceso a A.__dict__ es proporcionado por el type.__dict__['__dict__'] (en teoría) que es lo mismo que vars(type)['__dict__'] .

La versión larga:

Tanto las clases como los objetos proporcionan acceso a los atributos a través del operador de atributo (implementado a través de la clase o el __getattribute__ la metaclase) y el atributo / protocolo __dict__ que es usado por vars(ob) .

Para los objetos normales, el objeto __dict__ crea un objeto dict separado, que almacena los atributos, y __getattribute__ primero intenta acceder a él y obtener los atributos desde allí (antes de intentar buscar el atributo en la clase utilizando el protocolo descriptor, y antes llamando a __getattr__ ). El descriptor __dict__ en la clase implementa el acceso a este diccionario.

  • x.name es equivalente a probarlos en orden: x.__dict__['name'] , type(x).name.__get__(x, type(x)) , type(x).name
  • x.__dict__ hace lo mismo pero se salta el primero por razones obvias

Como es imposible que el __dict__ de la instance se almacene en el __dict__ de la instancia, se accede directamente a través del protocolo descriptor, y se almacena en un campo especial en la instancia.

Un escenario similar es cierto para las clases, aunque su __dict__ es un objeto proxy especial que pretende ser un diccionario (pero puede que no sea internamente), y no le permite cambiarlo o reemplazarlo por otro. Este proxy le permite, entre todo lo demás, acceder a los atributos de una clase que le son específicos y no están definidos en una de sus bases.

De forma predeterminada, un vars(cls) de una clase vacía lleva tres descriptores: __dict__ para almacenar los atributos de las instancias, __weakref__ , que es utilizado internamente por weakref , y la cadena de documentación de la clase. Los dos primeros pueden desaparecer si define __slots__ . Entonces no __weakref__ atributos __dict__ y __weakref__ , sino que tendrías un único atributo de clase para cada ranura. Los atributos de la instancia no se almacenarán en un diccionario, y los descriptores respectivos de la clase proporcionarán acceso a ellos.


Y, por último, la incoherencia de que A.__dict__ es diferente de A.__dict__['__dict__'] se debe a que el atributo __dict__ es, por excepción, nunca se buscó en los vars(A) , así que lo que es cierto no es cierto para Prácticamente cualquier otro atributo que usarías. Por ejemplo, A.__weakref__ es lo mismo que A.__dict__['__weakref__'] . Si esta incoherencia no existiera, el uso de A.__dict__ no funcionaría, y tendría que usar siempre vars(A) lugar.

Como A.__dict__ es un diccionario que almacena atributos A , A.__dict__['__dict__'] es la referencia directa a ese mismo atributo A.__dict__ .

A.__dict__ contiene una (tipo de) referencia a sí mismo. La parte “tipo de” es por qué la expresión A.__dict__ devuelve un dictproxy lugar de un dict normal.

 >>> class B(object): ... "Documentation of B class" ... pass ... >>> B.__doc__ 'Documentation of B class' >>> B.__dict__  >>> B.__dict__['__doc__'] 'Documentation of B class' 

¡Vamos a explorar!

 >>> A.__dict__['__dict__']  

Me pregunto qué es eso.

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

¿Qué atributos tiene un objeto getset_descriptor ?

 >>> type(A.__dict__["__dict__"]).__dict__  

Al hacer una copia de ese dictproxy , podemos encontrar algunos atributos interesantes, específicamente __objclass__ y __name__ .

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

Entonces, __objclass__ es una referencia a A y __name__ es solo la cadena '__dict__' , ¿quizás el nombre de un atributo?

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

¡Ahí lo tenemos! A.__dict__['__dict__'] es un objeto que puede referirse a A.__dict__ .

Puedes probar el siguiente ejemplo simple para entender más de esto:

 >>> class A(object): pass ... >>> a = A() >>> type(A)  >>> type(a)  >>> type(a.__dict__)  >>> type(A.__dict__)  >>> type(type.__dict__)  >>> type(A.__dict__['__dict__'])  >>> type(type.__dict__['__dict__'])  >>> 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 

Del ejemplo anterior, parece que los atributos de los objetos de clase se almacenan por su clase, los atributos de la clase se almacenan por su clase, que son metaclases. Esto también es validado por:

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