__slots__ for Memory Optimization
Overview
By default, Python objects store their instance attributes in a dynamic dictionary (`__dict__`). The `__slots__` attribute allows you to explicitly declare which attributes an instance can have. When you define `__slots__`, Python suppresses the creation of `__dict__` and allocates a fixed amount of space for the specified attributes. Think of `__dict__` as a flexible, expanding cargo container that takes up a lot of space, whereas `__slots__` is like a custom-molded foam insert that perfectly and rigidly fits only the tools you need. Python enforces a strict layout constraint when combining multiple slotted classes. You cannot inherit from multiple classes that define non-empty `__slots__`: class BaseA: __slots__ = ('a',) class BaseB: __slots__ = ('b',) class Child(BaseA, BaseB): # TypeError: multiple bases have instance lay-out conflict pass This limitation exists because Python must calculate a single, fixed memory layout for the instance structure.
Using `__slots__` drastically reduces the memory footprint of objects, which is critical when creating millions of instances. It also provides faster attribute access.
Where used: Pydantic, SQLAlchemy, Discord.py
Why learn this
- Optimizing memory in applications that process millions of rows or objects
- Preventing accidental creation of new attributes due to typos
Code walkthrough
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x)
Focus: Notice how explicitly defining `__slots__` prevents the creation of a `__dict__` dictionary, making the `Point` objects more memory-efficient.
Common mistakes
- Inheritance without `__slots__`: If a subclass does not define `__slots__` but inherits from a class that does, the subclass will still get a `__dict__`. To maintain the memory savings, every subclass must define `__slots__` (even an empty tuple `()`).
- Inability to add new attributes: Because `__dict__` is not created, you cannot assign attributes that are not declared in `__slots__`. Trying to set `obj.new_attr = 5` will raise an `AttributeError`.
Glossary
- memory footprint
- The total amount of computer memory or RAM that a program or object uses while running. For example, a class using `__slots__` uses less memory per instance. Example: `__slots__ = ('x', 'y')`.
- rigidly
- In a strict, inflexible way that cannot be easily changed or bent. A slotted object holds its attributes rigidly, raising an `AttributeError` for undeclared attributes. Example: `obj.new_attr = 1`.
Recall questions
- How does defining `__slots__` save memory compared to normal Python objects?
- What happens if you try to assign a new, undeclared attribute to an object that uses `__slots__`?
- What happens if a subclass attempts to inherit from multiple base classes that each define non-empty `__slots__`?
- What happens if a subclass attempts to inherit from multiple base classes that each define non-empty `__slots__`?
Understanding checks
Why does a class with `__slots__` raise an `AttributeError` when you try to assign a new attribute not defined in the slots?
Because the class no longer has a dynamic `__dict__` to store arbitrary attributes.
When `__slots__` is defined, Python suppresses the creation of the `__dict__` and only allocates fixed space for the declared attributes, preventing any new attributes from being added dynamically.
Identify the bug in this inheritance setup that prevents it from saving memory.
`Point3D` does not define `__slots__`.
If a subclass doesn't define `__slots__`, Python will create a `__dict__` for it, defeating the memory optimization. `Point3D` should define `__slots__ = ('z',)`.
Identify why this class definition fails to compile.
A class cannot inherit from multiple base classes that define non-empty `__slots__`.
Python raises a `TypeError` because it cannot resolve the layout conflict between multiple fixed-memory bases. Only one parent can have non-empty `__slots__`.
Identify why this class definition fails to compile.
A class cannot inherit from multiple base classes that define non-empty `__slots__`.
Python raises a `TypeError` because it cannot resolve the layout conflict between multiple fixed-memory bases. Only one parent can have non-empty `__slots__`.
Practice tasks
Define a slotted class
The `Point` class uses a standard dictionary to store attributes. Modify the class to use `__slots__` for the `x` and `y` attributes to optimize memory.
Challenge
Fix the slotted inheritance
The `BaseEvent` class defines `__slots__` to save memory. However, `ClickEvent`, which inherits from `BaseEvent` and adds a `button` attribute, is consuming too much memory because instances are still being created with a `__dict__`. Fix `ClickEvent` so that it fully utilizes `__slots__` and saves memory.
Continue learning
Previous: Everything is an object in Python
Previous: Dictionaries: operations and use cases