Explain __dict__ attribute
Asked Answered
W

3

160

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?

Whoa answered 11/11, 2013 at 13:20 Comment(4)
You should read this: docs.python.org/2/reference/datamodel.htmlKerbstone
No one's going to be able to alleviate your confusion if we don't understand what you're confused about. If you tried to explain that, then you might have an on topic question for the site.Farlee
To avoid duplication, this question could be extended to asking how dict conversion works.Extrinsic
This version of the question is clearly better than what it was closed with before. I've reversed the direction of duplicate closure.Pyles
H
139

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}
Harragan answered 11/11, 2013 at 13:24 Comment(5)
Hi , if in the definition of func was the statement z=10 why in the output of func.__dict__ i see {} ? Lets say that the statement func.temp=1 next existed.Whoa
@Whoa __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
dict will not print 'a' : 1 since it is not set in the init methodFaux
@Faux We are not talking about member variables here. If you see the print statement, we are printing the __dict__ of the class itself, not an instance of it.Harragan
I tried this on a default data type in Python and it errored AttribueError, any reason for this.Junco
P
18

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__?

  • Is foo a writable attribute of a?
  • Is bar a writable attribute of a?
  • Is baz a writable attribute of a?
  • Is _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>>
}
Primordium answered 16/11, 2022 at 15:55 Comment(3)
One can also use dir() in lieu of inspect: dict((k, getattr(d, k)) for k in dir(d) if k[0] != '_')Memoirs
@LeonChang That's right, but dir() has some pitfalls. inspect.getmembers() uses dir() and handles these pitfalls. github.com/python/cpython/blob/3.11/Lib/inspect.py#L550Primordium
__dict__ contains the data stored in the program's memory for this specific object., amazing explanation!Spathic
F
5

__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.

Feliks answered 15/11, 2022 at 2:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.