118

I wonder why a class __dict__ is a mappingproxy, but an instance __dict__ is just a plain dict

>>> class A:
...     pass

>>> a = A()
>>> type(a.__dict__)
<class 'dict'>
>>> type(A.__dict__)
<class 'mappingproxy'>
dreftymac
  • 31,404
  • 26
  • 119
  • 182
José Luis
  • 3,713
  • 4
  • 33
  • 37

3 Answers3

115

This helps the interpreter assure that the keys for class-level attributes and methods can only be strings.

Elsewhere, Python is a "consenting adults language", meaning that dicts for objects are exposed and mutable by the user. However, in the case of class-level attributes and methods for classes, if we can guarantee that the keys are strings, we can simplify and speed-up the common case code for attribute and method lookup at the class-level. In particular, the __mro__ search logic for new-style classes is simplified and sped-up by assuming the class dict keys are strings.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 44
    And for the curious: `mappingproxy` makes `class.__dict__` read-only, so that only `class.__setattr__` remains as an avenue for setting class attributes, and it is that method that [enforces the restriction](https://hg.python.org/cpython/file/ed694938c61a/Objects/object.c#l1132). – Martijn Pieters Sep 22 '15 at 15:37
  • 2
    This is also relevant for anyone overriding `type.__setattr__` (which is hopefully/probably a pretty small set), since you can't write to `__dict__`; you have to use `super()`. – Kevin Sep 23 '15 at 00:58
  • 8
    It also makes sure that if you give a class a new magic method, Python can update the relevant C-level slot. If you bypass this by using something like `gc.get_referents(FooClass.__dict__)[0]['__eq__'] = eqmethod`, instances of `FooClass` might not actually use `eqmethod` for `==` comparisons. – user2357112 Dec 11 '15 at 23:54
  • 1
    I guess anyone wanting to serialise a derived class into Json is just unlucky then :/ – Basic Jul 07 '16 at 09:59
  • @MartijnPieters To clarify, this read-only nature is the reason why class attributes can only be re-assigned via access through the class itself? And why an attempt to re-assign a class attribute via access through an instance results in a new instance attribute of the same name as the class attribute? – soporific312 Mar 26 '20 at 19:07
  • @soporific312: yes to the first, no to the second. Assignment to attribute names on an instance is handled by `type(instance).__setattr__(instance, name, value)` anyway, never through more direct routes, and has nothing to do with the read-only nature of the class `__dict__` attribute. – Martijn Pieters Mar 26 '20 at 21:25
  • @MartijnPieters I may understand instance attribute assignment better now if I am interpreting the implications of your comment correctly. If I have a class with class attribute "foo" and instance attribute of "bar" and I made the assignment "class_instance.foo = 3", the class dict is not even accessed because the compiler knows ahead of time that this is an assignment on an instance? Still, Beazley & Jones' use of descriptor class attributes (https://github.com/dabeaz/python-cookbook/blob/master/src/8/creating_a_new_kind_of_class_or_instance_attribute/example1.py) allows instance assignment? – soporific312 Mar 26 '20 at 22:09
  • @soporific312: comments are really not the place to discuss this; instance attribute assignment always goes through the class `__setattr__` method, which makes all the decisions as to where the value is to be stored. – Martijn Pieters Mar 26 '20 at 22:10
  • I'm trying to replace a class `__members__` mapping proxy with a new one, but the `__members__` attribute is also read only. I wonder if there's a way ... https://stackoverflow.com/questions/75587442/validate-pydantic-dynamic-float-enum-by-name-with-openapi-description – NeilG Mar 02 '23 at 08:27
22

A mappingproxy is simply a dict with no __setattr__ method.

You can check out and refer to this code.

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

mappingproxy is since Python 3.3. The following code shows dict types:

class C:pass
ci=C()
print(type(C.__dict__)) #<class 'mappingproxy'>
print(type(ci.__dict__)) #<class 'dict'>
prosti
  • 42,291
  • 14
  • 186
  • 151
17

Since Python 3.3 mappingproxy type was renamed from dictproxy. There was an interesting discussion on this topic.

It's a little bit hard to find the documentation for this type, but the documentation for vars method describes this perfectly (though it wasn't documented for a while):

Objects such as modules and instances have an updateable __dict__ attribute; however, other objects may have write restrictions on their __dict__ attributes (for example, classes use a types.MappingProxyType to prevent direct dictionary updates).

If you need to assign a new class attribute you could use setattr. It worth to note that mappingproxy is not JSON serializable, check out the issue to understand why.


Also the history of this type is a quite interesting:

  • Python 2.7: type(A.__dict__) returns <type 'dict'> as type(dict()), and it's possible to assign new attributes through __dict__, e.g. A.__dict__['foo'] = 'bar'.
  • Python 3.0 - 3.2: type(A.__dict__) returns <class 'dict_proxy'>, the difference is introduced. Trying to assign a new attribte gives TypeError. There was an attempt to add dictproxy as a public built-in type.
  • Python 3.3: adds the <class 'mappingproxy'> type described above.
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
funnydman
  • 9,083
  • 4
  • 40
  • 55