Gyaan

__slots__

advanced slots memory optimization attributes

By default, every Python object stores its attributes in a dictionary (__dict__). This is flexible — we can add any attribute at any time — but it costs memory. When we have millions of instances of the same class, that memory adds up fast.

__slots__ tells Python: “These are the only attributes this class will ever have.” Python then uses a more compact internal structure instead of a dict.

How It Works

class Point:
    __slots__ = ("x", "y")

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

p = Point(3, 4)
print(p.x)  # 3

p.z = 5  # AttributeError: 'Point' object has no attribute 'z'

No __dict__ is created. Attributes are stored in fixed-size slots, like a struct in C.

Why Use It?

Two main reasons:

  1. Memory savings — each instance uses significantly less memory (no per-instance dict)
  2. Faster attribute access — direct offset lookup instead of dict hash lookup

The savings matter when we have many instances:

import sys

class WithDict:
    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

a = WithDict(1, 2)
b = WithSlots(1, 2)
print(sys.getsizeof(a.__dict__))  # ~104 bytes (the dict itself)
print(sys.getsizeof(a))           # ~48 bytes (the object)
print(sys.getsizeof(b))           # ~48 bytes (no dict overhead at all)

With a million instances, the dict overhead alone can be hundreds of megabytes.

No Dynamic Attributes

The trade-off is clear: we lose the ability to add arbitrary attributes at runtime.

class User:
    __slots__ = ("name", "email")

    def __init__(self, name, email):
        self.name = name
        self.email = email

u = User("Manish", "m@example.com")
u.age = 25  # AttributeError — 'age' not in __slots__

If we need some flexibility, we can include __dict__ in our slots:

class FlexUser:
    __slots__ = ("name", "__dict__")  # fixed + dynamic attributes

    def __init__(self, name):
        self.name = name

u = FlexUser("Manish")
u.age = 25  # works — stored in __dict__

But this partially defeats the purpose.

Inheritance Caveats

Slots get tricky with inheritance:

  • If a parent has __slots__ and a child doesn’t define __slots__, the child gets a __dict__ (losing the benefit)
  • Both parent and child should define __slots__ for full savings
  • Don’t repeat parent slots in the child
class Base:
    __slots__ = ("x",)

class Child(Base):
    __slots__ = ("y",)  # only new attributes here

c = Child()
c.x = 1  # from Base's slots
c.y = 2  # from Child's slots

With Dataclasses (Python 3.10+)

Dataclasses support slots natively with the slots=True parameter. This is the cleanest way to use them.

from dataclasses import dataclass

@dataclass(slots=True)
class Point:
    x: float
    y: float

p = Point(3.0, 4.0)
print(p.x)  # 3.0
p.z = 5     # AttributeError

No need to define __slots__ manually — the dataclass decorator handles it.

When to Use slots

  • Many instances of the same class (data processing, game entities, ORM rows)
  • Known, fixed attributes that won’t change
  • Performance-critical code where memory or speed matters

When NOT to use:

  • Prototyping or small scripts (not worth the hassle)
  • Classes that need dynamic attributes
  • When we’re not creating many instances

In simple language, __slots__ is us telling Python “I know exactly what attributes this class needs — please be efficient about it.” We give up flexibility for speed and memory savings.