Warum ist eine Klasse __dict__ ein Mapping-Proxy?

76

Ich frage mich, warum eine Klasse __dict__eine ist mappingproxy, aber eine Instanz __dict__ist nur eine Ebenedict

>>> class A:
...     pass

>>> a = A()
>>> type(a.__dict__)
<class 'dict'>
>>> type(A.__dict__)
<class 'mappingproxy'>
José Luis
quelle

Antworten:

79

Auf diese Weise kann der Interpreter sicherstellen, dass die Schlüssel für Attribute und Methoden auf Klassenebene nur Zeichenfolgen sein können.

An anderer Stelle ist Python eine "zustimmende Sprache für Erwachsene", was bedeutet, dass Diktate für Objekte vom Benutzer offengelegt und veränderbar sind. Wenn wir jedoch bei Attributen und Methoden auf Klassenebene für Klassen garantieren können, dass die Schlüssel Zeichenfolgen sind, können wir den allgemeinen Fallcode für die Suche nach Attributen und Methoden auf Klassenebene vereinfachen und beschleunigen. Insbesondere wird die Suchlogik __mro__ für Klassen neuen Stils vereinfacht und beschleunigt, indem angenommen wird, dass die Klassendiktatschlüssel Zeichenfolgen sind.

Raymond Hettinger
quelle
28
Und für Neugierige: mappingproxymacht class.__dict__schreibgeschützt, so dass nur class.__setattr__noch ein Weg zum Festlegen von Klassenattributen bleibt, und es ist diese Methode, die die Einschränkung erzwingt .
Martijn Pieters
2
Dies ist auch relevant für alle, die überschreiben type.__setattr__(was hoffentlich / wahrscheinlich ein ziemlich kleiner Satz ist), da Sie nicht schreiben können __dict__; du musst verwenden super().
Kevin
4
Außerdem wird sichergestellt, dass Python den entsprechenden C-Level-Slot aktualisieren kann, wenn Sie einer Klasse eine neue magische Methode geben. Wenn Sie dies umgehen, indem Sie so etwas wie verwenden gc.get_referents(FooClass.__dict__)[0]['__eq__'] = eqmethod, werden Instanzen von FooClassmöglicherweise nicht eqmethodfür ==Vergleiche verwendet.
user2357112 unterstützt Monica
Ich denke, jeder, der eine abgeleitete Klasse in Json serialisieren möchte, hat dann einfach Pech: /
Basic
@MartijnPieters Zur Verdeutlichung ist diese schreibgeschützte Natur der Grund, warum Klassenattribute nur über den Zugriff über die Klasse selbst neu zugewiesen werden können. Und warum führt ein Versuch, ein Klassenattribut über den Zugriff über eine Instanz neu zuzuweisen, zu einem neuen Instanzattribut mit demselben Namen wie das Klassenattribut?
einschläfernd312
10

Ein Mapping-Proxy ist einfach ein Diktat ohne __setattr__Methode.

Sie können diesen Code auschecken und darauf verweisen.

from types import MappingProxyType
d={'key': "value"}
m = MappingProxyType(d)
print(type(m)) # <class 'mappingproxy'>

m['key']='new' #TypeError: 'mappingproxy' object does not support item assignment

Zuordnungsproxy ist seit Python 3.3. Der folgende Code zeigt Diktattypen:

class C:pass
ci=C()
print(type(C.__dict__)) #<class 'mappingproxy'>
print(type(ci.__dict__)) #<class 'dict'>
Prosti
quelle
3

Da Python 3.3 mappingproxywurde Typ umbenannt aus dictproxy. Es gab eine interessante Diskussion zu diesem Thema.

Es ist ein bisschen schwierig, die Dokumentation für diesen Typ zu finden, aber die Dokumentation für die vars- Methode beschreibt dies perfekt (obwohl sie eine Weile nicht dokumentiert war ):

Objekte wie Module und Instanzen verfügen über ein aktualisierbares __dict__ Attribut. Bei anderen Objekten können jedoch Schreibbeschränkungen für ihre __dict__Attribute gelten (z. B. verwenden Klassen einen types.MappingProxyType, um direkte Wörterbuchaktualisierungen zu verhindern).

Wenn Sie ein neues Klassenattribut zuweisen müssen, können Sie es verwenden setattr. Beachten Sie, dass mappingproxyJSON nicht serialisierbar ist. Überprüfen Sie das Problem, um zu verstehen, warum.


Auch die Geschichte dieses Typs ist sehr interessant:

  • Python 2.7: type(A.__dict__) gibt <type 'dict'>as zurück type(dict())und es ist möglich, neue Attribute zuzuweisen __dict__, z A.__dict__['foo'] = 'bar'.

  • Python 3.0 - 3.2: type(A.__dict__) kehrt zurück <class 'dict_proxy'>, der Unterschied ist jetzt etwas deutlicher. Der Versuch, ein neues Attribut zuzuweisen, ergibt TypeError. Es wurde versucht , dictproxyeinen öffentlich eingebauten Typ hinzuzufügen.

  • Python 3.3: Fügt <class 'mappingproxy'>den oben beschriebenen Typ hinzu .
Funnydman
quelle