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.