2011-02-02 54 views
55
>>> 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 va a A.__dict__. ¿Qué es este <attribute '__dict__' of 'A' objects> encontrado en A.__dict__.__dict__, y cuándo contiene algo?¿Qué es el atributo __dict __.__ dict__ de una clase de Python?

+6

Una variable de ejemplo más adecuada hubiera sido 'ive'. Al menos que hubiera hecho esto un mayor 'Un .__ dict __ [ 'ive']' pregunta;) Voy a ver a mi misma – Joakim

Respuesta

77

En primer lugar es diferente de A.__dict__.__dict__A.__dict__['__dict__'], y el primero no existe. Este último es el atributo __dict__ que las instancias de la clase tendrían. 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 objeto __dict__, por lo que se accede a través de un descriptor definido en la clase.

Para entender esto, tendrías que leer el documentation of the descriptor protocol.

La versión corta:

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

La versión larga:

Ambas clases y objetos proporcionan acceso a los atributos tanto a través del operador de atributo (implementado a través de la clase o metaclase de __getattribute__), y la __dict__ atributo/protocolo que se utiliza por vars(ob).

Para los objetos normales, el objeto __dict__ crea una dict objeto separado, que almacena los atributos, y __getattribute__ primeros intentos de acceder a ella y obtener los atributos de allí (antes de intentar buscar el atributo en la clase utilizando el descriptor protocolo, y antes de llamar al __getattr__). El descriptor __dict__ en 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 se salta el primero por razones obvias

Como es imposible que el __dict__ de instance a se almacenará en __dict__ de la instancia, se accede directamente a través del protocolo de 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 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 los casos, __weakref__ que se utiliza internamente por weakref, y la cadena de documentación de la clase. Los primeros dos podrían desaparecer si define __slots__. Entonces no tendrías los 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, no ha buscado en el vars(A), así que lo que es verdad porque no es cierto para prácticamente cualquier otra atributo que usarías. Por ejemplo, A.__weakref__ es lo mismo que A.__dict__['__weakref__']. Si esta incoherencia no existiera, usar A.__dict__ no funcionaría, y en su lugar siempre tendría que usar vars(A).

+5

Gracias por la respuesta detallada. Aunque tuve que leerlo un par de veces, creo que ha pasado un tiempo desde que aprendí tantos detalles nuevos de Python. – porgarmingduod

7

Dado que 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 referencia (tipo de) a sí mismo. La parte "tipo de" es por qué la expresión A.__dict__ devuelve dictproxy en lugar de 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' 
+5

'Un .__ dict __ [ '__ dict __']' no es una referencia a '.__ dict__ Un '. Implementa el atributo '__dict__' de las instancias. Para probar esto por sí mismo, 'Un .__ dict __ [ '__ dict __'] .__ llegar __ (A(), A)' devuelve los atributos del 'A()', mientras que 'Un .__ dict __ [ '__ dict __'] .__ llegar __ (A, tipo) 'falla. –

5

¡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í __objclass__ es una referencia a A y __name__ es sólo la cadena '__dict__', nombre de un atributo, tal vez?

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

¡Lo tenemos! A.__dict__['__dict__'] es un objeto que puede consultar de nuevo A.__dict__.

+0

PEP 252 dice que '__objclass__' es la clase que _defined_ este atributo, no es un atributo de esa clase. Esto hace que tu ejemplo 'getattr' sea incorrecto. A una más correcta sería 'getattr (A() .__ dict __ [ '__ __ dict'] .__ objclass__, A .__ dict __ [ '__ __ dict'] .__ nombre __)' –

4

Puede probar el siguiente ejemplo sencillo de entender 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 

En el ejemplo anterior, parece objetos de esa clase atributos se almacenan por su clase, los atributos de clase se almacenan 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 
Cuestiones relacionadas