Course Progress77%
🍎 Python Advanced Python Topic 77 / 100
⏳ 8 min read

__slots__

Declare exactly which attributes a class can have to reduce memory usage and speed up attribute access for large object counts.

"__slots__ trades flexibility for speed and memory. When you have thousands of objects of the same class, the difference is real and measurable."

— ShurAI

How Python Normally Stores Attributes

By default, every Python object carries a __dict__ — a dictionary that stores all its attributes. This is flexible (you can add any attribute anytime) but uses memory and is slower to look up:

python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p.__dict__)   # {'x': 1, 'y': 2} — dict overhead for every instance

# You can add any attribute at runtime (flexible but unpredictable)
p.color = "red"    # works — even if you never intended this

__slots__ — Declare Allowed Attributes

Define __slots__ to tell Python exactly which attributes an instance can have. Python replaces the __dict__ with a compact fixed-size structure for each slot:

python
class PointSlotted:
    __slots__ = ["x", "y"]      # only these attributes allowed

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = PointSlotted(1, 2)
print(p.x)   # 1  — works normally

# p.__dict__          → AttributeError: no __dict__
# p.color = "red"     → AttributeError: 'color' not in __slots__

The Memory and Speed Benefit

Memory: 1 million Point objects
✗ Without __slots__
Each object carries a __dict__.
~56 bytes per instance baseline
+ dict overhead per object.
✓ With __slots__
Fixed-size slot descriptors.
~40% less memory per instance.
Also faster attribute access.
python — measuring the difference
import sys

class Normal:
    def __init__(self, x, y):
        self.x, self.y = x, y

class Slotted:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x, self.y = x, y

n = Normal(1, 2)
s = Slotted(1, 2)

print(f"Normal:  {sys.getsizeof(n)} bytes")   # Normal:  48 bytes
print(f"Slotted: {sys.getsizeof(s)} bytes")   # Slotted: 32 bytes

When to Use __slots__

✓ Good candidate
Creating thousands or millions of instances (data records, particles, pixels)
Class attributes are fixed and well-known
Performance-critical inner loops
✗ Skip __slots__ when
You need dynamic attributes at runtime
You need pickling or __dict__-based tools
Only a handful of instances — no measurable benefit

Real Example — Particle Simulation

python
class Particle:
    """Compact particle for a physics simulation.
    Millions of these may exist at once."""
    __slots__ = ["x", "y", "vx", "vy", "mass"]

    def __init__(self, x, y, vx=0.0, vy=0.0, mass=1.0):
        self.x, self.y   = x, y
        self.vx, self.vy  = vx, vy
        self.mass         = mass

    def move(self, dt):
        self.x += self.vx * dt
        self.y += self.vy * dt

# Create 1,000,000 particles efficiently
particles = [Particle(i * 0.01, i * 0.01) for i in range(1_000_000)]
for p in particles:
    p.move(0.016)

"__slots__ is a micro-optimisation you should reach for deliberately, not by default. Measure first. If memory or speed is genuinely a bottleneck and your class is simple, then slots are the right tool."

— ShurAI

🧠 Quiz — Q1

What does Python use by default to store instance attributes, and why is it a cost?

🧠 Quiz — Q2

What does defining __slots__ = ["x", "y"] in a class do?

🧠 Quiz — Q3

What happens if you try to add a new attribute to a slotted instance that is not in __slots__?

🧠 Quiz — Q4

When is __slots__ most beneficial?