__dict__ python class metaprogramming magic-methods

__dict__ python



¿Cuál es el atributo__dict__.__ dict__ de una clase de Python? (4)

>>> 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 hago A.something = 10 , esto entra en A.__dict__ . ¿Qué es este <attribute ''__dict__'' of ''A'' objects> encuentra en A.__dict__.__dict__ , y cuándo contiene algo?


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

A.__dict__ contiene una (especie de) referencia a sí mismo. La parte de "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__ <dictproxy object at 0x00B83590> >>> B.__dict__[''__doc__''] ''Documentation of B class''


En primer lugar, A.__dict__.__dict__ es diferente de A.__dict__[''__dict__''] , y el primero no existe. Este ú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 de descripción .

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__ lo proporciona 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 __getattribute__ la clase o metaclase) y del atributo / protocolo __dict__ que vars(ob) usa.

Para 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 __dict__ de la instancia, se accede directamente a través del protocolo del 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 no serlo 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 son específicos de ella y no están definidos en una de sus bases.

Por defecto, un vars(cls) de una clase vacía lleva tres descriptores: __dict__ para almacenar los atributos de las instancias, __weakref__ que es usado internamente por weakref , y la docstring de la clase. Los primeros dos podrían desaparecer si defines __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 inconsistencia que A.__dict__ es diferente de A.__dict__[''__dict__''] es porque el atributo __dict__ es, por excepción, nunca se buscó en vars(A) , entonces lo que es verdadero para eso no es verdad para prácticamente cualquier otro atributo que usarías. Por ejemplo, A.__weakref__ es lo mismo que A.__dict__[''__weakref__''] . Si no existiera esta incoherencia, usar A.__dict__ no funcionaría, y en su lugar siempre tendría que usar vars(A) .


Puede probar el siguiente ejemplo simple para comprender más de esto:

>>> 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

A partir del ejemplo anterior, parece que los atributos de los objetos de clase son almacenados por su clase, los atributos de clase son almacenados por su clase, que son metaclases. Esto también está validado por:

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


Vamos a explorar!

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

Me pregunto qué es eso?

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

¿Qué atributos tiene un objeto getset_descriptor ?

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

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__ (<class ''__main__.A''>, ''__dict__'')

Así que __objclass__ es una referencia a A y __name__ es solo la cadena ''__dict__'' , tal vez 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__ .