Qual è l'attributo __dict __.__ dict__ di una classe Python?


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

Se lo faccio A.something = 10, questo va in A.__dict__. Che cosa è questo <attribute '__dict__' of 'A' objects>Trovato in A.__dict__.__dict__, e quando lo fa contiene qualcosa?


11
Una variabile di esempio più adatta sarebbe stata ive. Almeno avrebbe reso questa una A.__dict__['ive']domanda più ;) Ci vedremo fuori
Joakim

Risposte:


110

Prima di tutto A.__dict__.__dict__è diverso da A.__dict__['__dict__'], e il primo non esiste. Quest'ultimo è l' __dict__attributo che avrebbero le istanze della classe. È un oggetto descrittore che restituisce il dizionario interno degli attributi per l'istanza specifica. In breve, l' __dict__attributo di un oggetto non può essere memorizzato negli oggetti __dict__, quindi vi si accede tramite un descrittore definito nella classe.

Per capirlo, dovresti leggere il documentazione del protocollo del descrittore .

La versione breve:

  1. Per un'istanza di classe A, l'accesso a instance.__dict__è fornito daA.__dict__['__dict__'] che è lo stesso di vars(A)['__dict__'].
  2. Per la classe A, l'accesso a A.__dict__è fornito da type.__dict__['__dict__'](in teoria) che è lo stesso di vars(type)['__dict__'].

La versione lunga:

Sia le classi che gli oggetti forniscono l'accesso agli attributi sia tramite l'operatore di attributo (implementato tramite la classe o la metaclasse __getattribute__), sia l' __dict__attributo / protocollo utilizzato da vars(ob).

Per gli oggetti normali, l' __dict__oggetto crea un dictoggetto separato , che memorizza gli attributi, e __getattribute__prima cerca di accedervi e di ottenere gli attributi da lì (prima di tentare di cercare l'attributo nella classe utilizzando il protocollo del descrittore e prima di chiamare __getattr__). Il __dict__descrittore sulla classe implementa l'accesso a questo dizionario.

  • x.nameè equivalente a cercare quelli in ordine: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ fa lo stesso ma salta il primo per ovvi motivi

Come è impossibile per la __dict__di instanceessere conservato in__dict__ dell'istanza, si accede attraverso il protocollo descrittore direttamente invece, ed è memorizzato in un campo speciale nel caso.

Uno scenario simile è vero per le classi, sebbene il loro __dict__ sia uno speciale oggetto proxy che finge di essere un dizionario (ma potrebbe non esserlo internamente) e non consente di modificarlo o sostituirlo con un altro. Questo proxy ti consente, tra tutte le altre, di accedere agli attributi di una classe che sono specifici per essa e non definiti in una delle sue basi.

Per impostazione predefinita, a vars(cls)di una classe vuota trasporta tre descrittori: __dict__per memorizzare gli attributi delle istanze, __weakref__che viene utilizzato internamente da weakref, e la docstring della classe. I primi due potrebbero essere spariti se definisci __slots__. Quindi non avresti attributi __dict__e __weakref__, ma avresti invece un singolo attributo di classe per ogni slot. Gli attributi dell'istanza quindi non verrebbero memorizzati in un dizionario e l'accesso ad essi sarà fornito dai rispettivi descrittori nella classe.


Infine, l'incongruenza che A.__dict__è diversa da A.__dict__['__dict__']è perché l'attributo __dict__, per eccezione, non è mai stato cercato vars(A), quindi ciò che è vero per non è vero praticamente per qualsiasi altro attributo che useresti. Ad esempio, A.__weakref__è la stessa cosa di A.__dict__['__weakref__']. Se questa incoerenza non esistesse, l'utilizzo A.__dict__non funzionerebbe e dovresti sempre usarla vars(A).


6
Grazie per la risposta dettagliata. Anche se ho dovuto leggerlo un paio di volte, penso che sia passato del tempo da quando ho imparato tanti nuovi dettagli di Python.
porgarmingduod

Perché esattamente l' __dict__attributo di un oggetto non può essere memorizzato in quello dell'oggetto __dict__?
zumgruenenbaum

2
@zumgruenenbaum Poiché ha lo __dict__scopo di memorizzare tutti gli attributi dell'istanza, un accesso agli attributi del modulo obj.xviene infine cercato sull'oggetto __dict__, vale a dire obj.__dict__['x']. Ora se __dict__non fosse implementato come descrittore questo porterebbe ad una ricorsione infinita, poiché per accedere obj.__dict__bisognerebbe cercarlo come obj.__dict__['__dict__']. Il descrittore aggira questo problema.
a_guest

11

Poiché A.__dict__è un dizionario che memorizza Aattributi, A.__dict__['__dict__']è il riferimento diretto a quello stesso A.__dict__attributo.

A.__dict__contiene un (tipo di) riferimento a se stesso. La parte "tipo" è il motivo per cui l'espressione A.__dict__restituisce a dictproxyinvece di normale dict.

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

9
A.__dict__['__dict__']non è un riferimento a A.__dict__. Implementa l' __dict__attributo delle istanze. Per provare tu stesso, A.__dict__['__dict__'].__get__(A(), A)restituisce gli attributi di A(), mentre A.__dict__['__dict__'].__get__(A, type)fallisce.
Rosh Oxymoron

10

Facciamo un po 'di esplorazione!

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

Mi chiedo cosa sia?

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

Quali attributi ha un getset_descriptoroggetto?

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

Facendone una copia dictproxypossiamo trovare alcuni attributi interessanti, in particolare __objclass__e __name__.

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

Quindi __objclass__un riferimento a Aed __name__è solo la stringa '__dict__', forse il nome di un attributo?

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

Ce l'abbiamo! A.__dict__['__dict__']è un oggetto a cui può fare riferimento A.__dict__.


PEP 252 dice che __objclass__è la classe che ha definito questo attributo, non che è un attributo di quella classe. Questo rende il tuo getattresempio errato. Uno più corretto sarebbegetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron

1
@RoshOxymoron La tua espressione si alza KeyError: '__dict__', contrariamente a quella di @ AndrewClark.
Maggyero

9

Puoi provare il seguente semplice esempio per capire di più:

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

Dall'esempio precedente, sembra che gli attributi degli oggetti di classe siano memorizzati dalla loro classe, gli attributi della classe siano memorizzati dalla loro classe, che sono metaclassi. Questo è anche convalidato da:

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

1
Stranamente, se isviene sostituito ==nel secondo confronto, ovvero A.__dict__ is type.__dict__['__dict__'].__get__(A), il risultato è Falsesia in python 2.7.15+ che in 3.6.8.
Arne Vogel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.