Python __slots__: Write Memory-Efficient Classes Like a Pro

When Python applications scale, memory often becomes the hidden bottleneck. A service that performs flawlessly with thousands of objects can struggle when handling millions. One underused but powerful feature that directly addresses this is __slots__.

This article explains what __slots__ is, why it matters, when to use it, and how to apply it in production-quality code.


The Problem: Why Normal Python Objects Are Heavy

By default, every Python object stores its attributes in a dynamic dictionary (__dict__). This provides flexibility but costs memory and adds lookup overhead.

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

Each instance of Point internally contains:

  • A PyObject header
  • A pointer to __dict__
  • A separate dictionary object
  • Hash table entries for attributes

For a handful of objects, this overhead is negligible. For millions, it becomes expensive.


What Is __slots__?

__slots__ tells Python to allocate a fixed layout for attributes instead of using a dictionary.

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

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

Now each instance stores values in a compact structure rather than a hash table.

Key effects:

  • Removes __dict__ from instances
  • Reduces memory usage significantly
  • Speeds up attribute access slightly
  • Prevents creation of undeclared attributes

Demonstrating the Difference (Colab-Ready)

Below is a practical benchmark you can run in Google Colab.

import tracemalloc
import gc
from dataclasses import dataclass

N = 1_000_000


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


@dataclass(slots=True)
class DataClassPoint:
    x: int
    y: int


def measure_memory(cls):
    gc.collect()
    tracemalloc.start()

    objs = [cls(i, i) for i in range(N)]

    current, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()

    del objs
    gc.collect()

    return current / (1024 * 1024)


print("Creating objects...\n")

print("Normal class:", measure_memory(NormalPoint), "MB")
print("Slotted class:", measure_memory(SlottedPoint), "MB")
print("Dataclass(slots=True):", measure_memory(DataClassPoint), "MB")

Typical outcome:

  • Normal class: Highest memory usage
  • Slotted class: 40–60% less memory
  • Dataclass with slots: Similar savings with cleaner syntax

Why Memory Drops So Much

Normal Instance Layout

Object header
Pointer to __dict__
Dictionary object
Hash table entries

Slotted Instance Layout

Object header
Fixed array of attributes

No dictionary, no hash table — just direct storage.


Preventing Accidental Attributes

Slotted classes reject unknown attributes, reducing bugs caused by typos.

p = SlottedPoint(1, 2)
p.z = 5  # Raises AttributeError

This can act as a lightweight structural guarantee in large systems.


Modern Best Practice: Dataclasses with Slots

Python 3.10 introduced built-in support for slots in dataclasses.

from dataclasses import dataclass

@dataclass(slots=True)
class Order:
    id: int
    symbol: str
    quantity: int
    price: float

    def value(self) -> float:
        return self.quantity * self.price

Why this is ideal for production:

  • Concise syntax
  • Automatic method generation
  • Type hints
  • Memory efficiency
  • Better maintainability

Real-World Use Cases

__slots__ shines when you create many objects with identical structure:

  • Market data and trading systems
  • Network packets and telemetry
  • Game engines
  • Large graph or geometry models
  • Simulation systems
  • Abstract syntax trees
  • Embedded or memory-constrained environments

Inheritance Considerations

Slots interact with inheritance in non-obvious ways.

class A:
    __slots__ = ("a",)

class B(A):
    __slots__ = ("b",)

If a subclass omits __slots__, it regains a __dict__, defeating the memory savings.


Limitations and Trade-offs

Use __slots__ carefully. It reduces flexibility.

Avoid it when:

  • You need dynamic attributes
  • You rely on frameworks expecting __dict__
  • You use complex multiple inheritance
  • You frequently modify class structure
  • You depend on generic serialization tools

Additionally, slotted classes cannot be weak-referenced unless you include "__weakref__" in the slot list.


When Should You Use It?

Use __slots__ when all of the following are true:

  • You create large numbers of objects
  • The attribute set is fixed
  • Memory efficiency matters
  • Performance is important
  • You control the class design

Otherwise, the default behavior is usually more practical.


Key Takeaway

__slots__ is not a beginner feature, but it is a powerful optimization tool for serious Python systems.

In one sentence:

__slots__ trades flexibility for memory efficiency, performance, and structural discipline.

For modern Python, the recommended approach is:

Use @dataclass(slots=True) for most production scenarios.


Leave a Comment

Your email address will not be published. Required fields are marked *