Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Python

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.

July 2026 5 min read 1 views 0 hearts

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

  1. No dynamic attributes: Once you define __slots__, you can’t set a new attribute that isn’t in the tuple. Trying p.z = 3 on a SlottedPoint raises AttributeError.

  2. 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.

  3. No __weakref__ by default: If you need weak references to your slotted objects, include '__weakref__' in the slots tuple.

  4. Less introspection friendly: Tools like vars(obj) won’t work on slotted instances (they raise TypeError), though dir() 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.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.