__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:
- Para una instancia de clase
A
, el acceso a lainstance.__dict__
es proporcionado porA.__dict__[''__dict__'']
que es lo mismo quevars(A)[''__dict__'']
. - Para la clase A, el acceso a
A.__dict__
lo proporciona eltype.__dict__[''__dict__'']
(en teoría) que es lo mismo quevars(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__
.