What is the __dict__.__dict__ attribute of a Python class?
Solution 1
First of all A.__dict__.__dict__
is different from A.__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:
- For an instance
a
of a classA
, access toa.__dict__
is provided byA.__dict__['__dict__']
which is the same asvars(A)['__dict__']
. - For a class
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 by vars(ob)
.
For normal objects, the __dict__
object creates a separate dict
object, 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.name
is 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 by weakref
, 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 from A.__dict__['__dict__']
is because the attribute __dict__
is, by exception, never looked up in vars(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 as A.__dict__['__weakref__']
. If this inconsistency didn't exist, using A.__dict__
would not work, and you'd have to always use vars(A)
instead.
Solution 2
You can try the following simple example to understand more of this:
>>> 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
From the above example, it seems that instance attributes are stored by their class, and class attributes are stored by their metaclass. This is also validated by:
>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
Solution 3
Lets do some exploring!
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>
I wonder what that is?
>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>
What attributes does a getset_descriptor
object have?
>>> type(A.__dict__["__dict__"]).__dict__
<dictproxy object at 0xb7efc4ac>
By making a copy of that dictproxy
we can find some interesting attributes, specifically __objclass__
and __name__
.
>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__
(<class '__main__.A'>, '__dict__')
So __objclass__
is a reference to A
and __name__
is just the string '__dict__'
, name of an attribute perhaps?
>>> getattr(A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) == A.__dict__
True
There we have it! A.__dict__['__dict__']
is an object that can refer back to A.__dict__
.
Related videos on Youtube
porgarmingduod
Updated on March 24, 2021Comments
-
porgarmingduod over 3 years
>>> 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 intoA.__dict__
. What is this<attribute '__dict__' of 'A' objects>
found inA.__dict__.__dict__
, and when does it contain something?-
Joakim over 8 yearsA more suitable example variable would've been
ive
. At least it would've made this a moreA.__dict__['ive']
question ;) I'll see myself out
-
-
Rosh Oxymoron over 13 yearsPEP 252 says that
__objclass__
is the class that defined this attribute, not that is an attribute of that class. This makes yourgetattr
example incorrect. A more correct one would begetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
-
porgarmingduod over 13 yearsThanks for the detailed answer. Although I had to read it a few times, I think it has been a while since I learned so many new details of Python.
-
Arne Vogel almost 5 yearsStrangely enough, if
is
is substituted for==
in the second comparison, i.e.A.__dict__ is type.__dict__['__dict__'].__get__(A)
, the result isFalse
in both python 2.7.15+ and 3.6.8. -
zumgruenenbaum over 4 yearsWhy exactly can't the
__dict__
attribute of an object be stored in the object's__dict__
? -
a_guest almost 4 years@zumgruenenbaum Since
__dict__
is meant to store all instance attributes, an attribute access of the formobj.x
is eventually looked up on the object's__dict__
, namelyobj.__dict__['x']
. Now if__dict__
was not implemented as a descriptor this would lead to an infinite recursion, since in order to accessobj.__dict__
you'd need to look it up asobj.__dict__['__dict__']
. The descriptor circumvents this problem. -
Maggyero almost 4 years@RoshOxymoron Your expression raises
KeyError: '__dict__'
, contrary to @AndrewClark’s. -
Maggyero over 3 years@ArneVogel This is because the expression
A.__dict__
(ortype.__dict__['__dict__'].__get__(A)
) evaluates to a newtypes.MappingProxyType
instance:A.__dict__ is not A.__dict__
. (More info here on the history of this type.) Contrarily to the expressiona.__dict__
(orA.__dict__['__dict__'].__get__(a)
) which evaluates to the samedict
instance:a.__dict__ is a.__dict__
.