Was ist das Attribut __dict __.__ dict__ einer Python-Klasse?

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

Wenn ich das tue A.something = 10, geht das in A.__dict__. Was ist diese <attribute '__dict__' of 'A' objects>gefunden A.__dict__.__dict__, und wann wird es etwas enthalten?

porgarmingduod
quelle
11
Eine geeignetere Beispielvariable wäre gewesen ive. Zumindest hätte es dies zu einer weiteren A.__dict__['ive']Frage gemacht;) Ich werde mich selbst sehen
Joakim

Antworten:

109

Erstens A.__dict__.__dict__ist anders als A.__dict__['__dict__'], und die erstere existiert nicht. Letzteres ist das __dict__Attribut, das die Instanzen der Klasse haben würden. Es ist ein Deskriptorobjekt, das das interne Wörterbuch der Attribute für die bestimmte Instanz zurückgibt. Kurz gesagt, das __dict__Attribut eines Objekts kann nicht in einem Objekt gespeichert werden __dict__, daher wird auf es über einen in der Klasse definierten Deskriptor zugegriffen.

Um dies zu verstehen, müssen Sie die Dokumentation des Deskriptorprotokolls lesen .

Die Kurzversion:

  1. Für eine Klasseninstanz Awird der Zugriff auf instance.__dict__bereitgestellt A.__dict__['__dict__'], der mit identisch ist vars(A)['__dict__'].
  2. Für die Klasse A wird der Zugriff auf (theoretisch) A.__dict__durch bereitgestellt, type.__dict__['__dict__']was derselbe ist wie vars(type)['__dict__'].

Die lange Version:

Sowohl Klassen als auch Objekte bieten Zugriff auf Attribute sowohl über den Attributoperator (implementiert über die Klasse oder die Metaklasse __getattribute__) als auch über das __dict__Attribut / Protokoll, das von verwendet wird vars(ob).

Bei normalen Objekten erstellt das __dict__Objekt ein separates dictObjekt, in dem die Attribute gespeichert werden, und __getattribute__versucht zunächst, darauf zuzugreifen und die Attribute von dort abzurufen (bevor versucht wird, das Attribut in der Klasse mithilfe des Deskriptorprotokolls zu suchen, und bevor es aufgerufen wird __getattr__). Der __dict__Deskriptor für die Klasse implementiert den Zugriff auf dieses Wörterbuch.

  • x.nameist äquivalent zu denen , um versuchen: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ macht das gleiche, überspringt aber den ersten aus offensichtlichen Gründen

Da es unmöglich ist, das __dict__of instancein __dict__der Instanz zu speichern , wird stattdessen direkt über das Deskriptorprotokoll darauf zugegriffen und in einem speziellen Feld in der Instanz gespeichert.

Ein ähnliches Szenario gilt für Klassen, obwohl __dict__es sich um ein spezielles Proxy-Objekt handelt, das sich als Wörterbuch ausgibt (möglicherweise jedoch nicht intern) und es Ihnen nicht ermöglicht, es zu ändern oder durch ein anderes zu ersetzen. Mit diesem Proxy können Sie unter anderem auf die Attribute einer Klasse zugreifen, die spezifisch für sie sind und nicht in einer ihrer Basen definiert sind.

Standardmäßig enthält a vars(cls)einer leeren Klasse drei Deskriptoren - __dict__zum Speichern der Attribute der Instanzen, __weakref__die intern von verwendet werden weakref, und der Dokumentzeichenfolge der Klasse. Die ersten beiden könnten weg sein, wenn Sie definieren __slots__. Dann würden Sie nicht haben __dict__und __weakref__Attribute, sondern würden Sie für jeden Steckplatz eine einzige Klasse Attribut haben. Die Attribute der Instanz würden dann nicht in einem Wörterbuch gespeichert, und der Zugriff darauf wird von den jeweiligen Deskriptoren in der Klasse bereitgestellt.


Und schließlich, die Inkonsistenz , die A.__dict__von dem unterscheidet, A.__dict__['__dict__']ist , da das Attribut __dict__ist, ausnahmsweise nie in nachgeschlagen vars(A), so was für sie richtig ist für praktisch jedes andere Attribut nicht wahr ist , dass Sie verwenden würde. Zum Beispiel A.__weakref__ist das gleiche wie A.__dict__['__weakref__']. Wenn diese Inkonsistenz nicht vorhanden A.__dict__wäre, würde die Verwendung nicht funktionieren, und Sie müssten vars(A)stattdessen immer verwenden .

Rosh Oxymoron
quelle
6
Danke für die ausführliche Antwort. Obwohl ich es ein paar Mal lesen musste, denke ich, dass es eine Weile her ist, seit ich so viele neue Details von Python gelernt habe.
Porgarmingduod
Warum kann das __dict__Attribut eines Objekts nicht genau im Objekt gespeichert werden __dict__?
zumgruenenbaum
2
@zumgruenenbaum Da __dict__alle Instanzattribute gespeichert werden sollen, obj.xwird schließlich ein Attributzugriff des Formulars auf dem Objekt nachgeschlagen __dict__, nämlichobj.__dict__['x'] . Wenn dies __dict__nicht als Deskriptor implementiert wäre, würde dies zu einer unendlichen Rekursion führen, da obj.__dict__Sie für den Zugriff nachschlagen müssten obj.__dict__['__dict__']. Der Deskriptor umgeht dieses Problem.
a_guest
10

Da A.__dict__ein Wörterbuch AAttribute speichert , A.__dict__['__dict__']ist der direkte Verweis auf dasselbe A.__dict__Attribut.

A.__dict__enthält einen (Art) Verweis auf sich selbst. Der "Art-of" -Teil ist, warum der Ausdruck A.__dict__ein dictproxyanstelle eines normalen zurückgibt 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'
vz0
quelle
9
A.__dict__['__dict__']ist kein Hinweis auf A.__dict__. Es implementiert das __dict__Attribut der Instanzen. Um dies selbst zu versuchen, werden A.__dict__['__dict__'].__get__(A(), A)die Attribute von zurückgegeben A(), während dies A.__dict__['__dict__'].__get__(A, type)fehlschlägt.
Rosh Oxymoron
10

Lass uns etwas erkunden!

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

Ich frage mich, was das ist?

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

Welche Attribute hat ein getset_descriptorObjekt?

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

Wenn wir eine Kopie davon dictproxyerstellen, können wir einige interessante Attribute finden, insbesondere __objclass__und __name__.

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

Ist __objclass__also ein Verweis auf Aund __name__nur die Zeichenfolge '__dict__', vielleicht der Name eines Attributs?

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

Da haben wir es! A.__dict__['__dict__']ist ein Objekt, auf das zurückgegriffen werden kann A.__dict__.

Andrew Clark
quelle
Laut PEP 252 __objclass__ist dies die Klasse, die dieses Attribut definiert hat, nicht das Attribut dieser Klasse. Dies macht Ihr getattrBeispiel falsch. Eine korrektere wäregetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron
1
@RoshOxymoron Ihr Ausdruck KeyError: '__dict__'wird im Gegensatz zu @ AndrewClark erhöht.
Maggyero
9

Sie können das folgende einfache Beispiel ausprobieren, um mehr davon zu verstehen:

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

Aus dem obigen Beispiel geht hervor, dass Klassenobjektattribute von ihrer Klasse gespeichert werden, die Klassenattribute von ihrer Klasse, die Metaklassen sind. Dies wird auch bestätigt durch:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
damaZhang
quelle
1
Seltsamerweise isist das Ergebnis , wenn es ==im zweiten Vergleich ersetzt wird, dh sowohl in Python 2.7.15+ als auch in 3.6.8. A.__dict__ is type.__dict__['__dict__'].__get__(A)False
Arne Vogel