I am really confused about the __dict__
attribute. I have searched a lot but still I am not sure about the output.
Could someone explain the use of this attribute from zero, in cases when it is used in a object, a class, or a function?
I am really confused about the __dict__
attribute. I have searched a lot but still I am not sure about the output.
Could someone explain the use of this attribute from zero, in cases when it is used in a object, a class, or a function?
Basically it contains all the attributes which describe the object in question. It can be used to alter or read the attributes.
Quoting from the documentation for __dict__
A dictionary or other mapping object used to store an object's (writable) attributes.
Remember, everything is an object in Python. When I say everything, I mean everything like functions, classes, objects etc (Ya you read it right, classes. Classes are also objects). For example:
def func():
pass
func.temp = 1
print(func.__dict__)
class TempClass:
a = 1
def temp_function(self):
pass
print(TempClass.__dict__)
will output
{'temp': 1}
{'__module__': '__main__',
'a': 1,
'temp_function': <function TempClass.temp_function at 0x10a3a2950>,
'__dict__': <attribute '__dict__' of 'TempClass' objects>,
'__weakref__': <attribute '__weakref__' of 'TempClass' objects>,
'__doc__': None}
__dict__
will hold attributes which describe the object. For a class, the variables inside it define the class but for a function, it is not. –
Harragan print
statement, we are printing the __dict__
of the class itself, not an instance of it. –
Harragan AttribueError
, any reason for this. –
Junco Python documentation defines __dict__
as:
A dictionary or other mapping object used to store an object’s (writable) attributes.
This definition is however a bit fuzzy, which leads to a lot of wrong usage of __dict__
.
Indeed, when you read this definition, can you tell what is a "writable" attribute and what it isn't?
Examples
Let's run a few examples showing how confusing and inaccurate it can be.
class A:
foo = 1
def __init__(self):
self.bar = 2
@property
def baz(self):
return self._baz
@bar.setter
def baz(self, value):
self._baz = value
>>> a = A()
>>> a.foo
1
>>> a.bar
2
Given the above class A
and knowing __dict__
's definition, can you guess what would be the value of a.__dict__
?
foo
a writable attribute of a
?bar
a writable attribute of a
?baz
a writable attribute of a
?_baz
a writable attribute of a
?Here is the answer:
>>> a.__dict__
{'bar': 2}
Surprisingly, foo
doesn't show up. Indeed, although accessible with a.foo
, it is an attribute of the class A
, not of the instance a
.
Now what happens if we define it explicitly as an attribute of a
?
>>> a.foo = 1
>>> a.__dict__
{'bar': 2, 'foo': 1}
From our point of view, nothing really changed, a.foo
is still equal to 1
, but now it shows up in __dict__
. Note that we can keep playing with it by deleting a.foo
for instance:
>>> del a.foo
>>> a.__dict__
{'bar': 2}
>>> a.foo
1
What happened here is that we deleted the instance attribute, and calling a.foo
falls back again to A.foo
.
Let's have a look at baz
now. We can assume that we can't find it in a.__dict__
because we didn't give it a value yet.
>>> a.baz = 3
Alright, now we defined a.baz
. So, is baz
a writable attribute? What about _baz
?
>>> a.__dict__
{'bar': 2, '_baz': 3}
From __dict__
's perspective, _baz
is a writable attribute, but baz
isn't. The explanation, again, is that baz
is an attribute of the class A
, not the instance a
.
>>> A.__dict__['baz']
<property at 0x7fb1e30c9590>
a.baz
is only an abstraction layer calling A.baz.fget(a)
behind the scenes.
Let's be even more sneaky with our dear friend and challenge its definition of "writable".
class B:
def __init__(self):
self.foobar = 'baz'
def __setattr__(self, name, value):
if name == 'foobar' and 'foobar' in self.__dict__:
# Allow the first definition of foobar
# but prevent any subsequent redefinition
raise TypeError("'foobar' is a read-only attribute")
super().__setattr__(name, value)
>>> b = B()
>>> b.foobar
'baz'
>>> b.foobar = 'bazbar'
TypeError: 'foobar' is a read-only attribute
>>> # From our developer's perspective, 'foobar' is not a writable attribute
>>> # But __dict__ doesn't share this point of view
>>> b.__dict__
{'foobar': 'baz'}
Then what is __dict__
exactly?
Thanks to the behavior noticed in the above examples, we can now have a better understanding of what __dict__
actually does. But we need to switch from the developer's to the computer's perspective:
__dict__
contains the data stored in the program's memory for this specific object.
That's it, __dict__
exposes what's actually stored in memory at our object's address.
Python documentation on data model also defines it as the object's namespace:
A class instance has a namespace implemented as a dictionary which is the first place in which attribute references are searched. When an attribute is not found there, and the instance’s class has an attribute by that name, the search continues with the class attributes.
However, I believe thinking of __dict__
as the object's memory table gives a much better visualization of what's included in this namespace, and what's not.
But! There is a catch...
You thought we were done with __dict__
?
__dict__
is not the way to deal with the object's memory footprint, but a way.
There is indeed another way: __slots__
. I won't detail here how it works, there is already a very complete answer about __slots__
if you want to learn more about it. But the important thing for us is that, if slots are defined:
class C:
__slots__ = ('foo', 'bar')
def __init__(self):
self.foo = 1
self.bar = 2
>>> c = C()
>>> c.foo
1
>>> c.bar
2
>>> c.__dict__
AttributeError: 'C' object has no attribute '__dict__'
We can say "goodbye" to __dict__
.
So when should I use __dict__
?
As we saw, __dict__
must be seen from the computer's perspective, not from the seat of the developer. More often than not, what we consider "attributes" of our object is not directly connected to what's actually stored in memory. Especially with the use of properties or __getattr__
for instance, that add a level of abstraction for our comfort.
Although the use of __dict__
to inspect the attributes of an object will work in most trivial cases, we can't rely 100% rely on it. Which is a shame for something used to write generic logic.
The use case of __dict__
should probably be limited to inspecting an object's memory contents. Which is not so common. And keep in mind that __dict__
might not be defined at all (or lack some attributes actually stored in memory) when slots are defined.
It can also be very useful in Python's console to quickly check a class' attributes and methods. Or an object's attributes (I know I just said we can't rely on it, but in the console who cares if it fails sometimes or if it's not accurate).
Thanks but... how do I browse my object's attribute then?
We saw that __dict__
is often misused and that we can't really rely on it to inspect an object's attributes. But then, what is the correct way to do it? Is there any way to browse an objects attributes from the developer's abstracted point of view?
Yes. There are several ways to do introspection, and the correct way will depend on what you actually want to get. Instance attributes, class attributes, properties, private attributes, even methods, ... Technically, all these are attributes, and according to your situation, you might want to include some but exclude others. The context is also important. Maybe you are using a library that already exposes the attributes you want through their API.
But in general, you can rely on the inspect
module.
class D:
foo = 1
__slots = ('bar', '_baz')
@property
def baz(self):
return self._baz
@baz.setter
def baz(self, value):
self._baz = value
def __init__(self):
self.bar = 2
self.baz = 3
def pointless_method(self):
pass
>>> d = D()
>>> import inspect
>>> dict((k, v) for k, v in inspect.getmembers(d) if k[0] != '_')
{
'bar': 2,
'baz': 3,
'foo': 1
}
>>> dict((k, getattr(d, k)) for k, v in inspect.getmembers(D) if inspect.isdatadescriptor(v) or inspect.isfunction(v))
{
'__init__': <bound method D.__init__ of <__main__.D object at 0x7fb1e26a5b40>>,
'_baz': 3,
'bar': 2,
'baz': 3,
'pointless_method': <bound method D.pointless_method of <__main__.D object at 0x7fb1e26a5b40>>
}
dict((k, getattr(d, k)) for k in dir(d) if k[0] != '_')
–
Memoirs dir()
has some pitfalls. inspect.getmembers()
uses dir()
and handles these pitfalls. github.com/python/cpython/blob/3.11/Lib/inspect.py#L550 –
Primordium __dict__ can get the instance variables (data attributes) in an object as a dictionary.
So, if there is Person
class below:
class Person:
x1 = "Hello"
x2 = "World"
def __init__(self, name, age):
self.name = name
self.age = age
def test1(self):
pass
@classmethod
def test2(cls):
pass
@staticmethod
def test3():
pass
obj = Person("John", 27)
print(obj.__dict__) # Here
__dict__ gets name
and age
with their values in a dictionary as shown below:
{'name': 'John', 'age': 27}
And, if the new instance variable gender
is added after instanciation as shown below:
# ...
obj = Person("John", 27)
obj.gender = "Male" # Here
print(obj.__dict__)
__dict__ gets name
, age
and gender
with their values in a dictionary as shown below:
{'name': 'John', 'age': 27, 'gender': 'Male'}
In addition, if using dir() as shown below:
# ...
obj = Person("John", 27)
obj.gender = "Male"
print(dir(obj)) # Here
We can get all in an object as a list as shown below:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'age', 'gender', 'name', 'test1', 'test2', 'test3', 'x1', 'x2']
And, as far as I researched and as I asked this question, there are no functions to get only the static or special variables or normal, class, static or special methods in an object in Python.
© 2022 - 2024 — McMap. All rights reserved.