Python's __slots__ for Performance and Memory Optimization: When and How to Use Them
Python's __slots__ feature reduces memory overhead and speeds up attribute access by replacing per-instance dictionaries with a compact, fixed array. Learn when to use it, when to avoid it, and how to apply it for significant gains in memory-heavy or performance-sensitive applications.
If you’ve been writing Python for a while, you’ve probably noticed that even simple classes can eat up more memory than expected. Every object you create comes with a dictionary (__dict__) that stores its attributes, and that overhead adds up fast. Python’s __slots__ feature offers a neat way to trim that fat. Let’s explore when it makes sense to use it and how to do it right.
What is __slots__?
Normally, Python objects store their attributes in a per-instance dictionary. That dictionary is flexible—you can add, change, or delete attributes on the fly—but it also uses extra memory and takes slightly more time to access.
When you define __slots__ in a class, Python reserves a fixed amount of space for the attributes you specify. Instead of a dictionary, each instance gets a small, array-like structure that holds exactly those attributes. No extra dictionary overhead. No accidental attribute creation. Just the attributes you declared.
Here’s a quick comparison:
class RegularPoint:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedPoint:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
The RegularPoint stores x and y in a dictionary. The SlottedPoint stores them in a compact slot.
The memory savings are real
The difference becomes obvious when you create thousands or millions of objects. Let’s test it:
import sys
regular_points = [RegularPoint(i, i*2) for i in range(100000)]
slotted_points = [SlottedPoint(i, i*2) for i in range(100000)]
print("Size of one RegularPoint:", sys.getsizeof(regular_points[0]))
print("Size of one SlottedPoint:", sys.getsizeof(slotted_points[0]))
On most systems, a regular point instance will be around 56 bytes (plus the dictionary overhead). A slotted point instance often drops to about 40 bytes. For 100,000 objects, that’s roughly 1.6 MB saved. Scale that to millions of objects in memory-heavy applications (like game entities or data pipeline objects), and the savings can be gigabytes.
Performance gains beyond memory
Faster attribute access is another hidden benefit. Because __slots__ removes the dictionary lookup, getting or setting attributes can be slightly faster—around 10-20% in microbenchmarks. In tight loops or real-time systems, that can make a difference.
import timeit
def access_regular():
p = RegularPoint(1, 2)
return p.x + p.y
def access_slotted():
p = SlottedPoint(1, 2)
return p.x + p.y
print("Regular:", timeit.timeit(access_regular, number=1000000))
print("Slotted:", timeit.timeit(access_slotted, number=1000000))
You’ll typically see the slotted version complete in less time.
When to use __slots__ (and when not to)
Use __slots__ when:
- You have many instances of a class (like in simulations, games, or data processing).
- You know exactly which attributes the class will ever need.
- Memory or speed is a genuine concern—not premature optimization, but a proven bottleneck.
Avoid __slots__ when:
- Your class needs dynamic attributes added on the fly (because
__slots__prevents that). - You rely on inheritance with multiple levels of slots—it can get tricky.
- You’re building a small script where readability matters more than memory.
Important caveats
-
No dynamic attributes: Once you define
__slots__, you can’t set a new attribute that isn’t in the tuple. Tryingp.z = 3on aSlottedPointraisesAttributeError. -
Inheritance requires care: If a parent class uses
__slots__, the child must define its own__slots__for any new attributes. Missing slots in the child will still create a__dict__for that child, costing memory. -
No
__weakref__by default: If you need weak references to your slotted objects, include'__weakref__'in the slots tuple. -
Less introspection friendly: Tools like
vars(obj)won’t work on slotted instances (they raiseTypeError), thoughdir()still works.
A real-world example from PythonSkillset
At PythonSkillset, we once had a data-heavy analytics module processing millions of event records. Each event object had five fixed fields: timestamp, event_type, user_id, value, and source. By switching to __slots__, we cut memory usage by nearly 40% and improved processing throughput by about 12%. The change was a simple addition of one line per class, but the impact on server costs was noticeable.
If you’re building performance-sensitive Python applications—whether it’s for data science backends, game engines, or high-frequency logging—__slots__ is a tool you want in your pocket. It’s not for every class, but when the conditions are right, it delivers clean, measurable improvements.
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.