>>> 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 lo hago A.something = 10
, esto entra en A.__dict__
. ¿Qué es esto <attribute '__dict__' of 'A' objects>
que se encuentran en A.__dict__.__dict__
, y cuándo se contiene algo?
python
class
metaprogramming
magic-methods
porgarmingduod
fuente
fuente
ive
. Al menos habría hecho de esta unaA.__dict__['ive']
pregunta más ;) MeRespuestas:
Primero que nada
A.__dict__.__dict__
es diferenteA.__dict__['__dict__']
y el primero no existe. Este último es el__dict__
atributo que tendrían las instancias de la clase. Es un objeto descriptor que devuelve el diccionario interno de atributos para la instancia específica. En resumen, el__dict__
atributo de un objeto no se puede almacenar en el objeto__dict__
, por lo que se accede a través de un descriptor definido en la clase.Para entender esto, tendría que leer la documentación del protocolo descriptor .
La versión corta:
A
, el acceso ainstance.__dict__
es proporcionado por loA.__dict__['__dict__']
que es lo mismo quevars(A)['__dict__']
.A.__dict__
lo proporcionatype.__dict__['__dict__']
(en teoría), que es el mismo quevars(type)['__dict__']
.La versión larga:
Tanto las clases como los objetos proporcionan acceso a los atributos tanto a través del operador de atributo (implementado a través de la clase o metaclase
__getattribute__
) como del__dict__
atributo / protocolo que utilizavars(ob)
.Para los objetos normales, el
__dict__
objeto crea undict
objeto 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 de llamar__getattr__
). El__dict__
descriptor de la clase implementa el acceso a este diccionario.x.name
es equivalente a tratar aquellos en orden:x.__dict__['name']
,type(x).name.__get__(x, type(x))
,type(x).name
x.__dict__
hace lo mismo pero omite el primero por razones obviasComo es imposible que el
__dict__
deinstance
se almacene en__dict__
la instancia, se accede a él 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
__dict__
hay un objeto proxy especial que pretende ser un diccionario (pero puede que no lo sea internamente) y no le permite cambiarlo o reemplazarlo por otro. Este proxy te permite, entre todo lo demás, acceder a los atributos de una clase que le son específicos y no definidos en una de sus bases.De forma predeterminada, una
vars(cls)
de una clase vacía lleva tres descriptores:__dict__
para almacenar los atributos de las instancias,__weakref__
que se usa internamenteweakref
, y la cadena de documentos de la clase. Los dos primeros podrían desaparecer si lo define__slots__
. Entonces no tendrías atributos__dict__
y__weakref__
, sino que tendrías un atributo de clase único para cada espacio. Los atributos de la instancia entonces no se almacenarían en un diccionario, y el acceso a ellos será proporcionado por los descriptores respectivos en la clase.Y, por último, la inconsistencia que
A.__dict__
es diferenteA.__dict__['__dict__']
es porque el atributo__dict__
, por excepción, nunca se buscavars(A)
, por lo que lo que es cierto no es cierto para prácticamente cualquier otro atributo que usaría. Por ejemplo,A.__weakref__
es lo mismo queA.__dict__['__weakref__']
. Si esta inconsistencia no existiera, el usoA.__dict__
no funcionaría y tendría que usar siempre en suvars(A)
lugar.fuente
__dict__
puede almacenar el atributo de un objeto en el del objeto__dict__
?__dict__
está destinado a almacenar todos los atributos de instancia, un acceso de atributo del formularioobj.x
finalmente se busca en el objeto__dict__
, a saberobj.__dict__['x']
. Ahora bien, si__dict__
no se implementó como un descriptor, esto conduciría a una recursión infinita, ya que para accederobj.__dict__
necesitaría buscarlo comoobj.__dict__['__dict__']
. El descriptor evita este problema.Dado que
A.__dict__
es un diccionario que almacenaA
atributos,A.__dict__['__dict__']
es la referencia directa a ese mismoA.__dict__
atributo.A.__dict__
contiene una (especie de) referencia a sí mismo. La parte "tipo de" es la razón por la que la expresiónA.__dict__
devuelve un endictproxy
lugar de un normaldict
.>>> 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'
fuente
A.__dict__['__dict__']
no es una referencia aA.__dict__
. Implementa el__dict__
atributo de las instancias. Para probar esto por sí mismo,A.__dict__['__dict__'].__get__(A(), A)
devuelve los atributos deA()
, mientrasA.__dict__['__dict__'].__get__(A, type)
falla.¡Exploremos un poco!
>>> A.__dict__['__dict__'] <attribute '__dict__' of 'A' objects>
Me pregunto que es eso
>>> type(A.__dict__['__dict__']) <type 'getset_descriptor'>
¿Qué atributos tiene un
getset_descriptor
objeto?>>> type(A.__dict__["__dict__"]).__dict__ <dictproxy object at 0xb7efc4ac>
Al hacer una copia de eso
dictproxy
podemos encontrar algunos atributos interesantes, específicamente__objclass__
y__name__
.>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__ (<class '__main__.A'>, '__dict__')
Entonces, ¿
__objclass__
es una referenciaA
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 al que se puede hacer referenciaA.__dict__
.fuente
__objclass__
es la clase que definió este atributo, no que sea un atributo de esa clase. Esto hace que sugetattr
ejemplo sea incorrecto. Uno más correcto seríagetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
KeyError: '__dict__'
, contrariamente a la de @ AndrewClark.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 la 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
fuente
is
se sustituye==
en la segunda comparación, es decirA.__dict__ is type.__dict__['__dict__'].__get__(A)
, el resultado estáFalse
en python 2.7.15+ y 3.6.8.