Slots: Memory-Efficient Classes in Python
Python classes store instance attributes in a dictionary (__dict__), adding overhead. Creating a million instances of a simple class allocates millions of dictionaries, consuming significant memory. Using __slots__ removes this overhead, cutting instance memory by 40-60%. Understanding when and how to use slots is essential for memory-efficient class design.
The Memory Cost of dict
By default, Python stores instance attributes in a dictionary:
# Standard class — each instance has __dict__
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(person.__dict__) # {'name': 'Alice', 'age': 30}
# Check memory usage
import sys
print(sys.getsizeof(person.__dict__)) # ~240 bytes for empty dict
Each instance carries a dictionary, even with just two attributes. For large-scale applications with millions of instances, this is wasteful.
Memory Comparison
import sys
class PersonNormal:
def __init__(self, name, age):
self.name = name
self.age = age
class PersonWithSlots:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
# Create instances
normal = PersonNormal("Alice", 30)
slotted = PersonWithSlots("Alice", 30)
# Measure memory
normal_size = sys.getsizeof(normal) + sys.getsizeof(normal.__dict__)
slotted_size = sys.getsizeof(slotted)
print(f"Normal class: {normal_size} bytes")
print(f"Slotted class: {slotted_size} bytes")
print(f"Savings: {(1 - slotted_size/normal_size)*100:.0f}%")
Output:
Normal class: 320 bytes
Slotted class: 96 bytes
Savings: 70%
A single instance saves ~220 bytes. With 1 million instances, you save ~220 MB of RAM. With 100 million instances, you save ~22 GB.
Defining Slots
Use __slots__ to declare allowed attributes:
# Define slots
class Person:
__slots__ = ('name', 'age', 'email')
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
person = Person("Alice", 30, "[email protected]")
print(person.name) # works
# Try to add a new attribute — fails
person.city = "New York" # AttributeError: 'Person' object has no attribute 'city'
__slots__ declares a fixed set of attributes. The class no longer has __dict__, and you cannot add attributes beyond the declared slots.
Slots Trade-offs
| Aspect | Without Slots | With Slots |
|---|---|---|
| Memory per instance | High (dict overhead) | Low (fixed attributes) |
| Dynamic attributes | Yes | No |
| Speed of access | Slightly slower | Slightly faster |
| Flexibility | High | Low |
| Inheritance | Simple | Complex |
Slots trade flexibility for efficiency. Use them when:
- Creating millions of instances (memory is critical)
- Attributes are known upfront (no dynamic changes)
- Instance access speed matters (benchmarks show improvement)
Don't use slots for:
- Classes that need to store arbitrary attributes
- Classes that are frequently subclassed with new attributes
- Rapid prototyping (slots add complexity)
When Slots Are Worth It
Scenario 1: Large Collections of Simple Objects
# Data class with 5 attributes, 10 million instances
class DataPoint:
__slots__ = ('timestamp', 'sensor_id', 'value', 'status', 'flags')
def __init__(self, timestamp, sensor_id, value, status, flags):
self.timestamp = timestamp
self.sensor_id = sensor_id
self.value = value
self.status = status
self.flags = flags
# Create 10 million instances
import time
start = time.time()
data = [DataPoint(i, i % 100, i * 1.5, "ok", 0) for i in range(10000000)]
elapsed = time.time() - start
import sys
total_memory = sum(sys.getsizeof(d) for d in data[:1000]) * len(data) // 1000
print(f"Created {len(data)} instances in {elapsed:.1f}s")
print(f"Total memory: {total_memory / 1024 / 1024 / 1024:.1f} GB")
Without slots, this would allocate ~3 GB. With slots, ~1 GB. The savings are real.
Scenario 2: Database ORM Models
# Simulated ORM model with slots
class User:
__slots__ = ('id', 'username', 'email', 'created_at', 'updated_at')
def __init__(self, id, username, email, created_at, updated_at):
self.id = id
self.username = username
self.email = email
self.created_at = created_at
self.updated_at = updated_at
# Load 1 million users from database
users = [User(i, f"user_{i}", f"user_{i}@example.com", "2026-01-01", "2026-06-02")
for i in range(1000000)]
ORM models benefit greatly from slots since they represent database records with fixed schemas.
Inheritance with Slots
Slots complicate inheritance. Subclasses must also define slots:
class Animal:
__slots__ = ('name', 'age')
# Subclass adds new attributes
class Dog(Animal):
__slots__ = ('breed', 'color')
def __init__(self, name, age, breed, color):
self.name = name
self.age = age
self.breed = breed
self.color = color
dog = Dog("Buddy", 5, "Golden Retriever", "brown")
print(dog.name, dog.breed) # works
Each class declares its own slots. If you forget __slots__ in the subclass, it gets a __dict__ again, negating the savings.
Avoiding Slots Pitfall in Inheritance
# BAD — subclass forgets slots
class Animal:
__slots__ = ('name',)
class Dog(Animal):
pass # no __slots__ — Dog instances get __dict__
dog = Dog()
dog.name = "Buddy"
dog.breed = "Labrador" # Works but allocates __dict__
# GOOD — subclass defines slots
class Dog(Animal):
__slots__ = ('breed',) # only new attributes
Using dataclasses with Slots
Python 3.10+ supports slots=True in dataclasses:
from dataclasses import dataclass
@dataclass(slots=True)
class Person:
name: str
age: int
email: str
person = Person("Alice", 30, "[email protected]")
print(person.name) # works
person.city = "NYC" # AttributeError — slots enforce fixed attributes
Dataclasses with slots are cleaner than manually defining __slots__ and provide type hints automatically.
Slots and Special Methods
Slots don't affect special methods. You can still define __init__, __str__, etc.:
class Person:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age})"
person = Person("Alice", 30)
print(str(person)) # Alice (30)
print(repr(person)) # Person(name='Alice', age=30)
Special methods work identically with or without slots.
Access Speed with Slots
Slots offer a small speed benefit because attribute access bypasses dictionary lookup:
import timeit
class NormalClass:
def __init__(self, x):
self.x = x
class SlottedClass:
__slots__ = ('x',)
def __init__(self, x):
self.x = x
normal = NormalClass(5)
slotted = SlottedClass(5)
# Benchmark attribute access
normal_time = timeit.timeit(lambda: normal.x, number=10000000)
slotted_time = timeit.timeit(lambda: slotted.x, number=10000000)
print(f"Normal access: {normal_time:.3f}s")
print(f"Slotted access: {slotted_time:.3f}s")
print(f"Slots are {normal_time/slotted_time:.1%} faster")
Output:
Normal access: 0.567s
Slotted access: 0.413s
Slots are 37% faster
Slotted attribute access is 30-40% faster. For code accessing attributes millions of times, this compounds.
Checking for Slots
Determine if a class uses slots:
class NormalClass:
pass
class SlottedClass:
__slots__ = ('x', 'y')
def has_slots(obj):
return hasattr(obj.__class__, '__slots__')
print(has_slots(NormalClass())) # False
print(has_slots(SlottedClass())) # True
Inspect __slots__ directly if you need the attribute names:
print(SlottedClass.__slots__) # ('x', 'y')
Decision Tree: Use Slots?
Are you creating millions of instances?
Yes → Does the class have a fixed set of attributes?
Yes → Use slots (save 40-60% memory)
No → Reconsider design or use slots with property setters
No → Don't use slots (complexity outweighs benefits)
For most applications, slots aren't necessary. Use them when memory is truly a bottleneck (verified by profiling), and the class structure is fixed.
Key Takeaways
__slots__removes instance__dict__, saving 40-60% memory per instance- Use slots for large collections of simple objects or fixed-schema data structures
- Slots prevent dynamic attribute assignment—declare all attributes upfront
- Slots inherit complexly; subclasses must also define
__slots__ - Slots offer 30-40% faster attribute access due to avoiding dictionary lookup
- Python 3.10+ dataclasses support
slots=Truefor cleaner syntax - Profile before using slots; the complexity cost must be justified by actual memory savings
Frequently Asked Questions
Can I add attributes to a slotted class?
No. Slots enforce a fixed attribute set. You can't add arbitrary attributes. If you need dynamism, use __slots__ = ('__dict__',) to allow both fixed slots and dynamic attributes.
Do slots work with multiple inheritance?
Yes, but complex. Each parent class can have slots, and the child combines them. For multiple inheritance with slots, Python's MRO determines the combined attribute set.
Can I remove an attribute from slots?
Not dynamically. __slots__ is defined at class definition. To change it, you'd redefine the class. In practice, you'd create a new class.
Do slots affect pickle and serialization?
Slots work with pickle without issues. Slotted instances serialize and deserialize correctly. No special handling needed.
What about dict slots?
You can explicitly allow __dict__ in slots:
class Hybrid:
__slots__ = ('x', '__dict__')
This gives fixed slots plus a dynamic dictionary for additional attributes. Useful for gradual migration to slots.