Saving Memory in Python with `__slots__`
Learn how Python's `__slots__` reduces memory overhead by replacing per-instance dictionaries with fixed-size arrays, with benchmarks and when to use it.
If you've been working with Python for a while, you know that objects can be quite memory-hungry. Every time you create a class instance, Python allocates a dictionary (__dict__) to hold its attributes. This dictionary is flexible but comes at a cost—sometimes a big one, especially when you're creating thousands or millions of objects.
That's where __slots__ comes in. It's a simple feature that can dramatically reduce memory usage in your Python programs. Let me walk you through what it does and when you should use it.
The Problem: Python's Default Object Storage
When you create a typical class in Python, each instance carries around a dictionary for attribute storage:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Pythonskillset", "contact@pythonskillset.com")
print(user.__dict__)
# Output: {'name': 'Pythonskillset', 'email': 'contact@pythonskillset.com'}
This dictionary is powerful—you can add, remove, or change attributes dynamically. But dictionaries are heavy. Each dictionary takes up significant memory, and for objects with a fixed set of attributes, this overhead is wasteful.
The Solution: __slots__
When you define __slots__, Python tells the interpreter to store attributes in a fixed-size array instead of a dictionary. No dictionary, no overhead. Here's the same class using slots:
class User:
__slots__ = ('name', 'email')
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Pythonskillset", "contact@pythonskillset.com")
print(hasattr(user, '__dict__'))
# Output: False
By defining __slots__, you're telling Python: "These are the only attributes this class will ever have." No dictionary is created, no extra memory is wasted.
How Much Memory Does It Save?
Let's test this with a real benchmark. We'll create a million user objects to see the difference:
import sys
class UserSlots:
__slots__ = ('name', 'email')
def __init__(self, name, email):
self.name = name
self.email = email
class UserNoSlots:
def __init__(self, name, email):
self.name = name
self.email = email
# Create one instance of each
slots_user = UserSlots("test", "test@example.com")
no_slots_user = UserNoSlots("test", "test@example.com")
print(f"Size with __slots__: {sys.getsizeof(slots_user)} bytes")
print(f"Size without __slots__: {sys.getsizeof(no_slots_user)} bytes")
You'll typically see a difference of about 40-60 bytes per object. That might not sound like much, but when you scale up to millions of objects, it can mean saving hundreds of megabytes of RAM.
When Should You Use __slots__?
Slots aren't always the right choice. Here's when they shine:
- Large numbers of objects - If you're creating thousands or millions of instances, slots can save significant memory
- Fixed attribute sets - When you know exactly what attributes an object will have and they're not changing
- Data-heavy applications - Data processing pipelines, game engines, or scientific computing where memory matters
- High-performance scenarios - Slots also give you faster attribute access (about 10-15% faster in some cases)
When to Avoid __slots__
- Dynamic attributes - If you need to add attributes on the fly, slots won't work
- Small number of objects - For just a few instances, the memory savings are negligible
- Inheritance complexity - Subclasses that use slots can get tricky (more on this below)
- When using
__dict__directly - Some libraries or frameworks expect objects to have a__dict__
The Inheritance Gotcha
Slots don't work automatically with inheritance. If a parent class uses slots and you create a subclass without them, the subclass will fall back to using dictionaries anyway:
class Base:
__slots__ = ('x',)
class Child(Base):
pass # This will have __dict__ again!
child = Child()
child.y = 10 # Works fine because __dict__ exists
If you want to maintain slot behavior in subclasses, you need to be explicit:
class Child(Base):
__slots__ = ('y',) # Must add slots for new attributes
child = Child()
# child.z = 10 # This would fail - AttributeError
Real-World Example at Pythonskillset
At Pythonskillset, we recently optimized a data processing pipeline that handled millions of user activity records. Each record was stored as a small Python object with fixed fields (user_id, action, timestamp, duration). By switching to __slots__, we reduced memory usage by roughly 60% and saw a measurable improvement in processing speed. The change took about 10 minutes to implement.
Final Thoughts
__slots__ is one of those Python features that's easy to overlook but incredibly valuable in the right situations. It's not a silver bullet—you still need to understand when it makes sense—but for memory-sensitive applications, it's a tool you should know well.
Remember: Python's flexibility is its strength, but that flexibility comes with costs. Knowing when to tighten things down with __slots__ is part of writing efficient, professional Python code.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.