Using __slots__ for Memory Efficiency in Python
If you’ve ever created thousands of Python objects and wondered why your memory usage exploded, __slots__ is the answer you’ve been looking for. This underused feature can cut memory consumption by 30-50% for classes with many instances, and it also speeds up attribute access. Most Python developers never touch it, which is a shame—it’s straightforward once you understand how it works.
How slots Works
By default, Python classes use a __dict__ to store instance attributes. This is flexible—you can add any attribute to any instance at runtime—but it comes with memory overhead. Every single object gets its own dictionary, which takes up space even if the object only has a couple of attributes.
__slots__ changes this. When you define __slots__ in a class, Python doesn’t create a __dict__ for instances. Instead, it allocates fixed memory slots for the attributes you specify:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
That’s it. The instances of Point can only have x and y attributes—try adding anything else and you’ll get an AttributeError. In exchange, you get memory savings and faster attribute access.
Compare this to the regular approach:
class PointRegular:
def __init__(self, x, y):
self.x = x
self.y = y
Both work identically from the outside, but under the hood, they’re very different.
Memory Benefits
Let’s look at some numbers. Here’s a quick comparison:
import sys
class Regular:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
regular = Regular(1, 2)
slots = WithSlots(1, 2)
print(f"Regular: {sys.getsizeof(regular)} bytes")
print(f"With slots: {sys.getsizeof(slots)} bytes")
On typical Python installations, the slotted version saves around 40-60 bytes per instance. That doesn’t sound like much until you’re creating millions of objects. At scale, the difference is substantial—think game development, data processing pipelines, or any scenario where you need hundreds of thousands of lightweight objects.
One caveat: the savings depend on the class. Classes with many attributes benefit more than those with just one or two.
Performance Benefits
Beyond memory, __slots__ improves attribute access speed. Since Python doesn’t need to look up attributes in a dictionary, it goes directly to the fixed offset in memory. The speedup is typically 10-20% for attribute access, which adds up in tight loops.
Here’s a rough benchmark:
import timeit
class Regular:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
regular_time = timeit.timeit(
"obj.x; obj.y",
setup="from __main__ import Regular; obj = Regular(1, 2)"
)
slots_time = timeit.timeit(
"obj.x; obj.y",
setup="from __main__ import WithSlots; obj = WithSlots(1, 2)"
)
print(f"Regular: {regular_time:.4f}")
print(f"With slots: {slots_time:.4f}")
The slotted version is consistently faster. It’s not a magic bullet—your database queries will still dominate runtime—but it’s a genuine optimization worth having in performance-critical code.
Common Pitfalls
__slots__ has some quirks you need to know about:
No __dict__: You can’t add arbitrary attributes to instances. This is usually fine, but it can break code that dynamically adds attributes.
Inheritance complications: If you inherit from a class with __slots__, the child class gets a __dict__ unless it also defines __slots__:
class Base:
__slots__ = ('x',)
class Child(Base):
__slots__ = ('y',) # Now has x and y, no __dict__
class BrokenChild(Base):
pass # Gets a __dict__ because it doesn't define __slots__
Multiple inheritance: If you use multiple inheritance, all parent classes must have __slots__ for the optimization to work.
Pickling: Slotted classes can be pickled, but you need to be careful with custom __reduce__ methods.
When NOT to Use It
Don’t reach for __slots__ everywhere. It’s not worth it when:
- You’re only creating a handful of objects
- Your classes need dynamic attributes
- You’re using frameworks that expect
__dict__(some ORMs do this) - You need multiple inheritance without careful planning
The classic use cases are data-heavy applications: game entities, scientific computations, caching structures, and anywhere you’re instantiating many objects of the same type.
Practical Examples
Here’s a practical example—a simple particle system for a game:
class Particle:
__slots__ = ('x', 'y', 'vx', 'vy', 'life', 'color')
def __init__(self, x, y, vx, vy, color):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.life = 1.0
self.color = color
def update(self, dt):
self.x += self.vx * dt
self.y += self.vy * dt
self.life -= dt
return self.life > 0
Creating 10,000 of these uses significantly less memory than the regular version. In a game running at 60fps with hundreds of particles on screen, that adds up.
Another example—a data row for processing:
class DataRow:
__slots__ = ('id', 'name', 'value', 'timestamp')
def __init__(self, id, name, value, timestamp):
self.id = id
self.name = name
self.value = value
self.timestamp = timestamp
Conclusion
__slots__ is one of those Python features that sits quietly, rarely discussed in tutorials but invaluable when you need it. It’s not a replacement for thinking about your data structures—sometimes a namedtuple or dataclass is the better choice—but it’s a tool worth having in your kit. If you’re building something that creates lots of objects and you know exactly what attributes those objects need, reach for __slots__. Your memory footprint will thank you.