__slots__ and Memory Optimization

Difficulty: Advanced

Question

What is __slots__ in Python? How does it improve memory and performance?

Answer

By default, each Python instance has a __dict__ dictionary to store attributes - flexible but memory-heavy.

__slots__ replaces __dict__ with a fixed-size array of slots for specified attributes. This: 1. Reduces memory by 30-50% per instance 2. Speeds up attribute access (array lookup vs dict lookup) 3. Prevents adding undeclared attributes at runtime

Best for: classes instantiated millions of times (data records, ORM rows, coordinate objects).

Limitations: cannot add new attributes at runtime, inheritance with __slots__ is complex, pickle/copy may need adjustments.

Code examples

__slots__ vs __dict__

import sys

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

class PointSlots:
    __slots__ = ('x', 'y')

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

d = PointDict(1.0, 2.0)
s = PointSlots(1.0, 2.0)

print(sys.getsizeof(d.__dict__))  # ~104 bytes (dict overhead)
print(hasattr(s, '__dict__'))     # False
print(sys.getsizeof(s))           # ~56 bytes (no dict)

# Cannot add undeclared attribute
try:
    s.z = 3.0
except AttributeError as e:
    print(e)  # 'PointSlots' object has no attribute 'z'

# Performance comparison (millions of instances)
import timeit

def create_dict():
    return [PointDict(i, i) for i in range(100000)]

def create_slots():
    return [PointSlots(i, i) for i in range(100000)]

print('Dict:', timeit.timeit(create_dict, number=10))
print('Slots:', timeit.timeit(create_slots, number=10))

Slots use a fixed descriptor array instead of a hash table. Memory savings of ~50 bytes per instance are significant at scale.

Slots with Inheritance

class Base:
    __slots__ = ('x',)

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

class Child(Base):
    __slots__ = ('y',)  # Only declare NEW slots

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

c = Child(1, 2)
print(c.x, c.y)  # 1 2

# If parent does NOT define __slots__, child gets __dict__
class RegularBase:
    pass

class MixedChild(RegularBase):
    __slots__ = ('x',)

m = MixedChild()
m.x = 1
m.z = 99  # Works! RegularBase provides __dict__

# Checking memory with dataclass slots (Python 3.10+)
from dataclasses import dataclass

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

import sys
print(sys.getsizeof(Coord(1.0, 2.0, 3.0)))

Child class only declares additional slots. If any base in the MRO lacks __slots__, a __dict__ is created anyway - defeating the purpose.

Key points

Concepts covered

__slots__, __dict__, Memory, Attribute Access, Performance