dir() does much more than __dict__ search
First of all, dir() is an API method that knows how to use attributes of type __dict__ to find attributes of an object.
However, not all objects have the __dict__ attribute. For example, if you must add the __slots__ attribute to your own class, instances of this class will not have the __dict__ attribute, but dir() can still display the available attributes in these instances:
>>> class Foo(object): ... __slots__ = ('bar',) ... bar = 'spam' ... >>> Foo().__dict__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Foo' object has no attribute '__dict__' >>> dir(Foo()) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar']
The same applies to many built-in types; list do not have the __dict__ attribute, but you can still list all the attributes with dir() :
>>> [].__dict__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'list' object has no attribute '__dict__' >>> dir([]) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
What dir() does with instances
Python instances have their own __dict__ , but their class:
>>> class Foo(object): ... bar = 'spam' ... >>> Foo().__dict__ {} >>> Foo.__dict__.items() [('__dict__', <attribute '__dict__' of 'Foo' objects>), ('__weakref__', <attribute '__weakref__' of 'Foo' objects>), ('__module__', '__main__'), ('bar', 'spam'), ('__doc__', None)]
The dir() method uses both of these __dict__ attributes, and one of them, object , to create a complete list of available attributes for the instance, class, and all ancestors of the class.
When you set attributes for a class, instances also see them:
>>> f = Foo() >>> f.ham Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Foo' object has no attribute 'ham' >>> Foo.ham = 'eggs' >>> f.ham 'eggs'
because the attribute is added to the __dict__ class:
>>> Foo.__dict__['ham'] 'eggs' >>> f.__dict__ {}
Note that the __dict__ instance remains empty. A search for attributes of Python objects follows a hierarchy of objects from an instance to enter parent classes to search for attributes.
Only when you set the attributes directly in the instance will you see the attribute reflected in the __dict__ instance, while the __dict__ class remains unchanged:
>>> f.stack = 'overflow' >>> f.__dict__ {'stack': 'overflow'} >>> 'stack' in Foo.__dict__ False
TL; DR; or summary
dir() not just looking for an __dict__ object (which sometimes doesn’t even exist), it will use the object’s inheritance (its class or type and any superclasses or parents of that class or type) to provide you with a complete picture of all available attributes.
__dict__ instance is only a "local" set of attributes in this instance and does not contain all the attributes available in the instance. Instead, you also need to look at class and class inheritance inheritance.