__slots__¶
By default, Python stores instance attributes in a dictionary (__dict__). The __slots__ declaration lets you explicitly define which attributes an instance can have, resulting in significant memory savings and faster attribute access.
The Problem: Memory Overhead¶
Every Python object with instance attributes carries a __dict__:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.__dict__) # {'x': 1, 'y': 2}
This dictionary: - Consumes memory (empty dict ~64 bytes, plus key storage) - Allows dynamic attribute creation - Has hash table overhead for attribute lookup
For millions of small objects, this overhead adds up significantly.
The Solution: __slots__¶
Declare __slots__ to use a fixed, memory-efficient storage:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x, p.y) # 1 2
# print(p.__dict__) # AttributeError: 'Point' object has no attribute '__dict__'
Memory Comparison¶
import sys
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
regular = RegularPoint(1, 2)
slotted = SlottedPoint(1, 2)
print(sys.getsizeof(regular)) # ~48 bytes (object only)
print(sys.getsizeof(regular.__dict__)) # ~104 bytes (dict overhead)
print(sys.getsizeof(slotted)) # ~48 bytes (no dict!)
# Total memory per instance:
# Regular: ~152 bytes
# Slotted: ~48 bytes (68% reduction!)
At Scale¶
# Creating 1 million points
regular_points = [RegularPoint(i, i) for i in range(1_000_000)]
# Memory: ~152 MB
slotted_points = [SlottedPoint(i, i) for i in range(1_000_000)]
# Memory: ~48 MB
# Savings: ~104 MB (68% reduction)
Syntax Variations¶
Tuple of Strings¶
class Point:
__slots__ = ('x', 'y')
List of Strings¶
class Point:
__slots__ = ['x', 'y']
Single Attribute¶
class Counter:
__slots__ = ('count',) # Note: tuple needs comma
# or
__slots__ = ['count']
# or
__slots__ = 'count' # Single string works too
Behavior Changes¶
No Dynamic Attributes¶
class SlottedPoint:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = SlottedPoint(1, 2)
p.z = 3 # AttributeError: 'SlottedPoint' object has no attribute 'z'
No __dict__ by Default¶
p = SlottedPoint(1, 2)
p.__dict__ # AttributeError: 'SlottedPoint' object has no attribute '__dict__'
Can Add __dict__ to Slots¶
class Hybrid:
__slots__ = ('x', 'y', '__dict__')
def __init__(self, x, y):
self.x = x
self.y = y
h = Hybrid(1, 2)
h.z = 3 # Works! Stored in __dict__
print(h.z) # 3
print(h.x) # 1 (stored in slot)
Inheritance¶
Slotted Parent, No Slots in Child¶
class Parent:
__slots__ = ('x',)
class Child(Parent):
pass # Gets __dict__ automatically
c = Child()
c.x = 1 # Uses inherited slot
c.y = 2 # Uses __dict__
print(c.__dict__) # {'y': 2}
Both Parent and Child Slotted¶
class Parent:
__slots__ = ('x',)
class Child(Parent):
__slots__ = ('y',) # Don't repeat 'x'!
c = Child()
c.x = 1
c.y = 2
# c.__dict__ # AttributeError - no dict
Warning: Don't Repeat Slots¶
class Parent:
__slots__ = ('x',)
class Child(Parent):
__slots__ = ('x', 'y') # BAD: 'x' repeated
# This wastes memory and can cause issues
With __weakref__¶
By default, slotted objects cannot be weakly referenced:
import weakref
class Slotted:
__slots__ = ('x',)
s = Slotted()
weakref.ref(s) # TypeError: cannot create weak reference to 'Slotted' object
Add __weakref__ to slots to enable weak references:
class Slotted:
__slots__ = ('x', '__weakref__')
s = Slotted()
ref = weakref.ref(s) # Works!
Performance Benefits¶
Faster Attribute Access¶
import timeit
class Regular:
def __init__(self):
self.x = 0
class Slotted:
__slots__ = ('x',)
def __init__(self):
self.x = 0
r = Regular()
s = Slotted()
# Attribute access is faster with slots
timeit.timeit('r.x', globals={'r': r}, number=10_000_000) # ~0.35s
timeit.timeit('s.x', globals={'s': s}, number=10_000_000) # ~0.30s
# ~15% faster
When to Use __slots__¶
Good Use Cases¶
# 1. Many instances of simple data classes
class Coordinate:
__slots__ = ('lat', 'lon')
# 2. Performance-critical inner loops
class Node:
__slots__ = ('value', 'left', 'right')
# 3. Memory-constrained environments
class SensorReading:
__slots__ = ('timestamp', 'value', 'sensor_id')
When NOT to Use¶
# 1. Need dynamic attributes
class FlexibleConfig:
pass # Users may add arbitrary attributes
# 2. Using __dict__ for introspection
class Debuggable:
pass # Need vars() or __dict__ access
# 3. Multiple inheritance with different slots
# Can get complicated quickly
Slots with Dataclasses (Python 3.10+)¶
Python 3.10 added slots=True to dataclasses:
from dataclasses import dataclass
@dataclass(slots=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
# p.z = 3 # AttributeError
This is the cleanest way to use slots with modern Python.
Slots with NamedTuple¶
NamedTuple already uses slots internally:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(1.0, 2.0)
# Already memory-efficient, but immutable
Common Pitfalls¶
Forgetting Inherited Slots¶
class Parent:
__slots__ = ('x',)
class Child(Parent):
__slots__ = ('y',)
def __init__(self):
self.x = 1 # From parent
self.y = 2 # From child
self.z = 3 # AttributeError! No slot for 'z'
Class Attributes vs Instance Slots¶
class Example:
__slots__ = ('x',)
y = 10 # Class attribute - works fine
def __init__(self):
self.x = 1 # Instance slot
# self.z = 3 # AttributeError
e = Example()
print(e.x) # 1 (instance)
print(e.y) # 10 (class attribute)
Default Values¶
# This doesn't work as expected:
class Wrong:
__slots__ = ('x',)
x = 10 # This becomes a class attribute, shadows the slot!
# Do this instead:
class Right:
__slots__ = ('x',)
def __init__(self, x=10):
self.x = x
Summary¶
| Aspect | With __dict__ |
With __slots__ |
|---|---|---|
| Memory per instance | Higher (~152 bytes) | Lower (~48 bytes) |
| Attribute access | Hash lookup | Direct offset |
| Dynamic attributes | Yes | No (unless __dict__ in slots) |
| Weak references | Yes | Only if __weakref__ in slots |
| Introspection | vars(obj) works |
Limited |
Key Takeaways:
__slots__eliminates per-instance__dict__overhead- Use for classes with many instances and fixed attributes
- Memory savings of 50-70% typical
- Slight performance improvement for attribute access
- Don't repeat parent slots in child classes
- Add
__weakref__if weak references needed - Python 3.10+ dataclasses support
slots=True