Python Class __dict__: Instance and Class Namespaces
python class **dict**: Understand how Python stores instance and class attributes in __dict__, how attribute lookup works, and when __slots__ removes the dictionary en...
The __dict__ attribute is the core of how a Python class stores state. When you work with python class **dict**, you are dealing with two separate namespaces: the instance __dict__, a regular dictionary holding per-object attributes, and the class __dict__, a read-only mapping that holds methods, class variables, and descriptors.
What __dict__ Holds on a Class vs an Instance
For instances of user-defined classes, __dict__ is a plain dictionary. When you assign self.x = 2 inside __init__, Python writes the key 'x' with value 2 into that dictionary.
class Point: def __init__(self, x, y): self.x = x self.y = y p = Point(2, 3) print(p.__dict__) # {'x': 2, 'y': 3}
The class object itself also has a __dict__, but it is a different object. It holds the class namespace: methods, class variables, and descriptors. It is wrapped in a mappingproxy, which is read-only.
class Point: label = "point" def __init__(self, x, y): self.x = x self.y = y def magnitude(self): return (self.x ** 2 + self.y ** 2) ** 0.5 print(Point.__dict__.keys()) # dict_keys(['__module__', 'label', '__init__', 'magnitude', '__dict__', '__weakref__', '__doc__'])
The instance __dict__ and the class __dict__ are separate. Assigning p.label = "other" creates an entry in p.__dict__ and shadows the class-level label for that instance only.
How Attribute Lookup Uses __dict__
When you read p.magnitude, Python does not search only p.__dict__. It first checks the instance dictionary, then walks the method resolution order of the class, checking each class's __dict__ in turn. This is why methods and class variables are visible from instances even though they are not stored in the instance namespace.
p = Point(2, 3) print(p.__dict__) # {'x': 2, 'y': 3} print('magnitude' in p.__dict__) # False print(Point.__dict__['magnitude']) # <function Point.magnitude at ...>
The lookup order matters when an instance attribute has the same name as a class attribute. The instance value wins because the instance dictionary is checked first.
Modifying __dict__ Directly
Because the instance __dict__ is a plain dictionary, you can read and write it directly.
p = Point(2, 3) p.__dict__['x'] = 10 print(p.x) # 10 p.__dict__['z'] = 5 print(p.z) # 5
This works, but it bypasses __setattr__ and any descriptor protocol on the class. If your class relies on __setattr__ for validation or coercion, direct writes to __dict__ will skip that logic. Direct manipulation is useful mainly for metaprogramming, debugging, or serialization code that needs to inspect or rebuild object state.
The class-level __dict__ is a mappingproxy and cannot be assigned to directly. You can still mutate class attributes through setattr(Point, 'label', 'other'), which goes through the normal attribute machinery.
__slots__ and When __dict__ Disappears
Defining __slots__ on a class changes how instances store attributes. Instead of a per-instance dictionary, Python allocates fixed slots for the listed attribute names.
class Point: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y p = Point(2, 3) print(p.__dict__) # AttributeError: 'Point' object has no attribute '__dict__'
With __slots__, instances no longer have a __dict__. Attempting to assign an attribute not listed in __slots__ raises AttributeError. This is the tradeoff: you lose dynamic attribute assignment and gain a smaller memory footprint.
You can opt back into a dictionary by including '__dict__' in __slots__:
class Point: __slots__ = ('x', 'y', '__dict__')
This restores the instance __dict__ while still keeping the compact slots for x and y.
Memory and Performance Implications
The main reason to use __slots__ is memory. A dictionary has significant overhead: the hash table, the entries, and the capacity that grows as keys are added. For a small object with two or three attributes, the dictionary can dominate the object's memory footprint. When a program holds millions of such objects, replacing the dictionary with slots can reduce memory usage substantially.
The performance effect of __slots__ on attribute access is usually small. Attribute reads through slots are slightly faster because they avoid a dictionary lookup, but the difference is rarely the bottleneck in real code. The decision to use __slots__ should be driven by memory constraints or by the need to prevent dynamic attribute creation, not by micro-optimization.
There is also a correctness angle. With __slots__, typos in attribute assignment fail fast instead of silently creating new attributes. That can be valuable in code where attribute names must stay consistent.
Practical Patterns and Edge Cases
One common pattern is using __dict__ to convert an object to a dictionary for serialization:
def to_dict(obj): return dict(obj.__dict__)
This works for simple objects but fails for instances of classes with __slots__. A more robust approach checks for __dict__ and falls back to slot names:
def to_dict(obj): if hasattr(obj, '__dict__'): return dict(obj.__dict__) return {name: getattr(obj, name) for name in getattr(obj, '__slots__', ())}
Another edge case: the __dict__ of a module is a regular dictionary, which is why module.attribute and module.__dict__['attribute'] are equivalent. Functions also have a __dict__, used for arbitrary attributes attached to the function object.
Finally, note that __dict__ itself appears in the class namespace. The entry Point.__dict__['__dict__'] is the descriptor that provides instance dictionaries. Removing or replacing it is not something you should do in normal code.