>>> 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?
If I do A.something = 10, this goes into A.__dict__. What is this <attribute '__dict__' of 'A' objects> found in A.__dict__.__dict__, and when does it contain something?
First of all
A.__dict__.__dict__is different fromA.__dict__['__dict__']. The former doesn't exist and the latter is the__dict__attribute that the instances of the class would have. It's a data descriptor object that returns the internal dictionary of attributes for the specific instance. In short, the__dict__attribute of an object can't be stored in object's__dict__, so it's accessed through a descriptor defined in the class.To understand this, you'd have to read the documentation of the descriptor protocol.
The short version:
aof a classA, access toa.__dict__is provided byA.__dict__['__dict__']which is the same asvars(A)['__dict__'].A, access toA.__dict__is provided bytype.__dict__['__dict__'](in theory) which is the same asvars(type)['__dict__'].The long version:
Both classes and objects provide access to attributes both through the attribute operator (implemented via the class or metaclass's
__getattribute__), and the__dict__attribute/protocol which is used byvars(ob).For normal objects, the
__dict__object creates a separatedictobject, which stores the attributes, and__getattribute__first tries to access it and get the attributes from there (before attempting to look for the attribute in the class by utilizing the descriptor protocol, and before calling__getattr__). The__dict__descriptor on the class implements the access to this dictionary.a.nameis equivalent to trying those in order:type(a).__dict__['name'].__get__(a, type(a))(only iftype(a).__dict__['name']is a data descriptor),a.__dict__['name'],type(a).__dict__['name'].__get__(a, type(a)),type(a).__dict__['name'].a.__dict__does the same but skips the second step for obvious reasons.As it's impossible for the
__dict__of an instance to be stored in itself, it's accessed through the descriptor protocol directly instead and is stored in a special field in the instance.A similar scenario is true for classes, although their
__dict__is a special proxy object that pretends to be a dictionary (but might not be internally), and doesn't allow you to change it or replace it with another one. This proxy allows you, among all else, to access the attributes of a class that are specific to it, and not defined in one of its bases.By default, a
vars(cls)of an empty class carries three descriptors:__dict__for storing the attributes of the instances,__weakref__which is used internally byweakref, and__doc__the docstring of the class. The first two might be gone if you define__slots__. Then you wouldn't have__dict__and__weakref__attributes, but instead you'd have a single class attribute for each slot. The attributes of the instance then wouldn't be stored in a dictionary, and access to them will be provided by the respective descriptors in the class.And lastly, the inconsistency that
A.__dict__is different fromA.__dict__['__dict__']is because the attribute__dict__is, by exception, never looked up invars(A), so what is true for it isn't true for practically any other attribute you'd use. For example,A.__weakref__is the same thing asA.__dict__['__weakref__']. If this inconsistency didn't exist, usingA.__dict__would not work, and you'd have to always usevars(A)instead.