Python Specific
Slots
When your application creates thousands or millions of small objects (like game entities, data points, or cache entries), memory usage can explode due to Python's per-instance dictionary overhead. Using __slots__ restricts objects to a fixed set of attributes, dramatically reducing memory consumption.
Be careful: if any class in the hierarchy lacks __slots__, instances get a __dict__ and lose the memory benefits.
When to Use slots
slots_definition.py
"""Basic __slots__ definition"""
# Without __slots__
print("Without __slots__:")
class PersonNoSlots:
def __init__(self, name, age):
self.name = name
self.age = age
person1 =
print(f"Name: {person1.name}, Age: {person1.age}")
print(f"Has __dict__: {hasattr(person1, '__dict__')}")
print(f"__dict__: {person1.__dict__}")
# Can add dynamic attributes
person1.email = "alice@example.com"
print(f"Added email: {person1.email}")
# With __slots__
print("\nWith __slots__:")
class PersonWithSlots:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
person2 = PersonWithSlots("Bob", 25)
print(f"Name: {person2.name}, Age: {person2.age}")
print(f"Has __dict__: {hasattr(person2, '__dict__')}")
# Cannot add dynamic attributes
try:
person2.email = "bob@example.com"
except AttributeError as e:
print(f"Error adding email: {e}")
# Tuple __slots__
print("\nTuple __slots__:")
class Point:
__slots__ = ('x', 'y') # Can use tuple instead of list
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(10, 20)
print(f"Point: {p}")
print(f"x={p.x}, y={p.y}")
# Multiple attributes
print("\nMultiple attributes:")
class User:
__slots__ = ['user_id', 'username', 'email', 'created_at']
def __init__(self, user_id, username, email, created_at):
self.user_id = user_id
self.username = username
self.email = email
self.created_at = created_at
def __repr__(self):
return f"User(id={self.user_id}, username='{self.username}')"
user = User(1, "alice", "alice@example.com", "2024-01-01")
print(f"User: {user}")
# Read-only slots
print("\nRead-only slots:")
class Config:
__slots__ = ['_host', '_port']
def __init__(self, host, port):
self._host = host
self._port = port
@property
def host(self):
return self._host
@property
def port(self):
return self._port
config = Config("localhost", 8080)
print(f"Config: {config.host}:{config.port}")
# Cannot modify (no setter)
try:
config.host = "0.0.0.0"
except AttributeError as e:
print(f"Error: {e}")
# Empty __slots__
print("\nEmpty __slots__:")
class Immutable:
__slots__ = [] # No instance attributes allowed
VALUE = 42 # Class attribute is OK
im = Immutable()
print(f"Class value: {im.VALUE}")
try:
im.x = 10
except AttributeError as e:
print(f"Error: {e}")
# Checking __slots__
print("\nChecking __slots__:")
class Example:
__slots__ = ['a', 'b', 'c']
print(f"__slots__: {Example.__slots__}")
print(f"Allowed attributes: {', '.join(Example.__slots__)}")
# Practical example
print("\nPractical example:")
class Vector2D:
__slots__ = ['x', 'y']
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
v3 = v1 + v2
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v3 = v1 + v2 = {v3}")
print(f"v1 magnitude = {v1.magnitude()}")
"""Basic __slots__ definition"""
# Without __slots__
print("Without __slots__:")
class PersonNoSlots:
def __init__(self, name, age):
self.name = name
self.age = age
person1 =
print(f"Name: {person1.name}, Age: {person1.age}")
print(f"Has __dict__: {hasattr(person1, '__dict__')}")
print(f"__dict__: {person1.__dict__}")
# Can add dynamic attributes
person1.email = "alice@example.com"
print(f"Added email: {person1.email}")
# With __slots__
print("\nWith __slots__:")
class PersonWithSlots:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
person2 = PersonWithSlots("Bob", 25)
print(f"Name: {person2.name}, Age: {person2.age}")
print(f"Has __dict__: {hasattr(person2, '__dict__')}")
# Cannot add dynamic attributes
try:
person2.email = "bob@example.com"
except AttributeError as e:
print(f"Error adding email: {e}")
# Tuple __slots__
print("\nTuple __slots__:")
class Point:
__slots__ = ('x', 'y') # Can use tuple instead of list
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(10, 20)
print(f"Point: {p}")
print(f"x={p.x}, y={p.y}")
# Multiple attributes
print("\nMultiple attributes:")
class User:
__slots__ = ['user_id', 'username', 'email', 'created_at']
def __init__(self, user_id, username, email, created_at):
self.user_id = user_id
self.username = username
self.email = email
self.created_at = created_at
def __repr__(self):
return f"User(id={self.user_id}, username='{self.username}')"
user = User(1, "alice", "alice@example.com", "2024-01-01")
print(f"User: {user}")
# Read-only slots
print("\nRead-only slots:")
class Config:
__slots__ = ['_host', '_port']
def __init__(self, host, port):
self._host = host
self._port = port
@property
def host(self):
return self._host
@property
def port(self):
return self._port
config = Config("localhost", 8080)
print(f"Config: {config.host}:{config.port}")
# Cannot modify (no setter)
try:
config.host = "0.0.0.0"
except AttributeError as e:
print(f"Error: {e}")
# Empty __slots__
print("\nEmpty __slots__:")
class Immutable:
__slots__ = [] # No instance attributes allowed
VALUE = 42 # Class attribute is OK
im = Immutable()
print(f"Class value: {im.VALUE}")
try:
im.x = 10
except AttributeError as e:
print(f"Error: {e}")
# Checking __slots__
print("\nChecking __slots__:")
class Example:
__slots__ = ['a', 'b', 'c']
print(f"__slots__: {Example.__slots__}")
print(f"Allowed attributes: {', '.join(Example.__slots__)}")
# Practical example
print("\nPractical example:")
class Vector2D:
__slots__ = ['x', 'y']
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
v3 = v1 + v2
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v3 = v1 + v2 = {v3}")
print(f"v1 magnitude = {v1.magnitude()}")
"""Basic __slots__ definition"""
# Without __slots__
print("Without __slots__:")
class PersonNoSlots:
def __init__(self, name, age):
self.name = name
self.age = age
person1 =
print(f"Name: {person1.name}, Age: {person1.age}")
print(f"Has __dict__: {hasattr(person1, '__dict__')}")
print(f"__dict__: {person1.__dict__}")
# Can add dynamic attributes
person1.email = "alice@example.com"
print(f"Added email: {person1.email}")
# With __slots__
print("\nWith __slots__:")
class PersonWithSlots:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
person2 = PersonWithSlots("Bob", 25)
print(f"Name: {person2.name}, Age: {person2.age}")
print(f"Has __dict__: {hasattr(person2, '__dict__')}")
# Cannot add dynamic attributes
try:
person2.email = "bob@example.com"
except AttributeError as e:
print(f"Error adding email: {e}")
# Tuple __slots__
print("\nTuple __slots__:")
class Point:
__slots__ = ('x', 'y') # Can use tuple instead of list
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(10, 20)
print(f"Point: {p}")
print(f"x={p.x}, y={p.y}")
# Multiple attributes
print("\nMultiple attributes:")
class User:
__slots__ = ['user_id', 'username', 'email', 'created_at']
def __init__(self, user_id, username, email, created_at):
self.user_id = user_id
self.username = username
self.email = email
self.created_at = created_at
def __repr__(self):
return f"User(id={self.user_id}, username='{self.username}')"
user = User(1, "alice", "alice@example.com", "2024-01-01")
print(f"User: {user}")
# Read-only slots
print("\nRead-only slots:")
class Config:
__slots__ = ['_host', '_port']
def __init__(self, host, port):
self._host = host
self._port = port
@property
def host(self):
return self._host
@property
def port(self):
return self._port
config = Config("localhost", 8080)
print(f"Config: {config.host}:{config.port}")
# Cannot modify (no setter)
try:
config.host = "0.0.0.0"
except AttributeError as e:
print(f"Error: {e}")
# Empty __slots__
print("\nEmpty __slots__:")
class Immutable:
__slots__ = [] # No instance attributes allowed
VALUE = 42 # Class attribute is OK
im = Immutable()
print(f"Class value: {im.VALUE}")
try:
im.x = 10
except AttributeError as e:
print(f"Error: {e}")
# Checking __slots__
print("\nChecking __slots__:")
class Example:
__slots__ = ['a', 'b', 'c']
print(f"__slots__: {Example.__slots__}")
print(f"Allowed attributes: {', '.join(Example.__slots__)}")
# Practical example
print("\nPractical example:")
class Vector2D:
__slots__ = ['x', 'y']
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D({self.x}, {self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
v3 = v1 + v2
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v3 = v1 + v2 = {v3}")
print(f"v1 magnitude = {v1.magnitude()}")
slots_memory_savings.py
"""Memory savings with __slots__"""
# Memory comparison
print("Memory comparison:")
import sys
class WithoutSlots:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class WithSlots:
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
# Create single instances
obj_no_slots = WithoutSlots(1, 2, 3)
obj_with_slots = WithSlots(1, 2, 3)
# Get sizes
size_no_slots = sys.getsizeof(obj_no_slots) + sys.getsizeof(obj_no_slots.__dict__)
size_with_slots = sys.getsizeof(obj_with_slots)
print(f"Without __slots__: {size_no_slots} bytes")
print(f"With __slots__: {size_with_slots} bytes")
print(f"Savings: {size_no_slots - size_with_slots} bytes ({100 * (1 - size_with_slots/size_no_slots):.1f}%)")
# Many instances
print("\nMany instances:")
# Create many instances
count = 100
no_slots_list = [WithoutSlots(i, i+1, i+2) for i in range(count)]
with_slots_list = [WithSlots(i, i+1, i+2) for i in range(count)]
# Estimate total size
total_no_slots = sum(sys.getsizeof(obj) + sys.getsizeof(obj.__dict__) for obj in no_slots_list[:100]) / 100 * count
total_with_slots = sum(sys.getsizeof(obj) for obj in with_slots_list[:100]) / 100 * count
print(f"{count} instances:")
print(f"Without __slots__: ~{total_no_slots/1024:.1f} KB")
print(f"With __slots__: ~{total_with_slots/1024:.1f} KB")
print(f"Estimated savings: ~{(total_no_slots - total_with_slots)/1024:.1f} KB")
# Simple data class
print("\nSimple data class:")
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# Create many points
points = [Point(i, i*2) for i in range(100)]
# Sample memory usage
sample_size = sys.getsizeof(points[0])
print(f"Point with __slots__: {sample_size} bytes each")
print(f"100 points: ~{sample_size * 100 / 1024:.1f} KB")
# Complex object
print("\nComplex object:")
class PersonNoSlots:
def __init__(self, name, age, email, city):
self.name = name
self.age = age
self.email = email
self.city = city
class PersonWithSlots:
__slots__ = ['name', 'age', 'email', 'city']
def __init__(self, name, age, email, city):
self.name = name
self.age = age
self.email = email
self.city = city
p1 = PersonNoSlots("Alice", 30, "alice@example.com", "NYC")
p2 = PersonWithSlots("Alice", 30, "alice@example.com", "NYC")
size1 = sys.getsizeof(p1) + sys.getsizeof(p1.__dict__)
size2 = sys.getsizeof(p2)
print(f"PersonNoSlots: {size1} bytes")
print(f"PersonWithSlots: {size2} bytes")
print(f"Savings per instance: {size1 - size2} bytes")
# Large dataset
print("\nLarge dataset:")
class Record:
__slots__ = ['id', 'timestamp', 'value', 'status']
def __init__(self, id, timestamp, value, status):
self.id = id
self.timestamp = timestamp
self.value = value
self.status = status
# Simulate large dataset
dataset_size = 10000
print(f"Creating {dataset_size} records...")
# Estimate memory (based on sample)
sample = Record(1, 1234567890, 42.5, "active")
record_size = sys.getsizeof(sample)
print(f"Size per record: {record_size} bytes")
print(f"Total for {dataset_size} records: ~{record_size * dataset_size / 1024 / 1024:.1f} MB")
# Time series data
print("\nTime series data:")
class DataPoint:
__slots__ = ['timestamp', 'value']
def __init__(self, timestamp, value):
self.timestamp = timestamp
self.value = value
# Simulate 1 year of per-minute data
minutes_per_year = 365 * 24 * 60
point_size = sys.getsizeof(DataPoint(0, 0.0))
print(f"Data point size: {point_size} bytes")
print(f"1 year (minute resolution): ~{point_size * minutes_per_year / 1024 / 1024:.1f} MB")
# Practical comparison
print("\nPractical comparison:")
# Coordinates for game
class CoordNoSlots:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class CoordWithSlots:
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
# Simulate game world with many entities
entity_count = 1000
# Sample sizes
no_slots = CoordNoSlots(0, 0, 0)
with_slots = CoordWithSlots(0, 0, 0)
size_no = sys.getsizeof(no_slots) + sys.getsizeof(no_slots.__dict__)
size_with = sys.getsizeof(with_slots)
print(f"{entity_count} game entities:")
print(f"Without __slots__: ~{size_no * entity_count / 1024:.1f} KB")
print(f"With __slots__: ~{size_with * entity_count / 1024:.1f} KB")
print(f"Memory saved: ~{(size_no - size_with) * entity_count / 1024:.1f} KB")
slots_no_dynamic_attrs.py
"""No dynamic attributes with __slots__"""
# Dynamic attributes blocked
print("Dynamic attributes blocked:")
class User:
__slots__ = ['name', 'email']
def __init__(self, name, email):
self.name = name
self.email = email
user = User("Alice", "alice@example.com")
# Allowed attributes work
print(f"Name: {user.name}")
print(f"Email: {user.email}")
# Modifying allowed attributes works
user.name = "Alice Smith"
print(f"Updated name: {user.name}")
# Adding new attribute fails
try:
user.age = 30
except AttributeError as e:
print(f"Error adding 'age': {e}")
try:
user.city = "NYC"
except AttributeError as e:
print(f"Error adding 'city': {e}")
# No __dict__
print("\nNo __dict__:")
class NoDict:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
obj = NoDict(10, 20)
# __dict__ doesn't exist
print(f"Has __dict__: {hasattr(obj, '__dict__')}")
# Can't access __dict__
try:
print(obj.__dict__)
except AttributeError as e:
print(f"Error: {e}")
# __slots__ is available
print(f"__slots__: {obj.__slots__}")
# Deleting attributes
print("\nDeleting attributes:")
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(5, 10)
print(f"Point: ({p.x}, {p.y})")
# Can delete slotted attributes
del p.x
print(f"Has x: {hasattr(p, 'x')}")
# Accessing deleted attribute fails
try:
print(p.x)
except AttributeError as e:
print(f"Error: {e}")
# Can reassign
p.x = 15
print(f"Reassigned x: {p.x}")
# Class attributes still work
print("\nClass attributes still work:")
class Config:
__slots__ = ['name']
# Class attributes are fine
DEFAULT_TIMEOUT = 30
VERSION = "1.0"
def __init__(self, name):
self.name = name
config = Config("prod")
print(f"Instance name: {config.name}")
print(f"Class DEFAULT_TIMEOUT: {config.DEFAULT_TIMEOUT}")
print(f"Class VERSION: {config.VERSION}")
# Can modify class attributes
Config.VERSION = "1.1"
print(f"Updated VERSION: {config.VERSION}")
# Methods still work
print("\nMethods still work:")
class Rectangle:
__slots__ = ['width', 'height']
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(10, 5)
print(f"Area: {rect.area()}")
print(f"Perimeter: {rect.perimeter()}")
# Properties work
print("\nProperties work:")
class Temperature:
__slots__ = ['_celsius']
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
temp = Temperature(25)
print(f"Celsius: {temp.celsius}")
print(f"Fahrenheit: {temp.fahrenheit}")
temp.celsius = 30
print(f"Updated - Celsius: {temp.celsius}, Fahrenheit: {temp.fahrenheit}")
# Weakref support
print("\nWeakref support:")
import weakref
# Without __weakref__ in __slots__, weakref fails
class NoWeakref:
__slots__ = ['value']
def __init__(self, value):
self.value = value
obj1 = NoWeakref(42)
try:
ref = weakref.ref(obj1)
except TypeError as e:
print(f"Error without __weakref__: {e}")
# Add __weakref__ to support weak references
class WithWeakref:
__slots__ = ['value', '__weakref__']
def __init__(self, value):
self.value = value
obj2 = WithWeakref(42)
ref = weakref.ref(obj2)
print(f"Weakref created: {ref()}")
print(f"Weakref value: {ref().value}")
# Pickle support
print("\nPickle support:")
import pickle
class PickleSlots:
__slots__ = ['name', 'value']
def __init__(self, name, value):
self.name = name
self.value = value
obj = PickleSlots("test", 123)
# Pickle and unpickle
pickled = pickle.dumps(obj)
restored = pickle.loads(pickled)
print(f"Original: {obj.name}, {obj.value}")
print(f"Restored: {restored.name}, {restored.value}")
# Practical example
print("\nPractical example:")
class ImmutablePoint:
__slots__ = ['_x', '_y']
def __init__(self, x, y):
object.__setattr__(self, '_x', x)
object.__setattr__(self, '_y', y)
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __setattr__(self, name, value):
raise AttributeError(f"Cannot modify immutable attribute '{name}'")
def __repr__(self):
return f"ImmutablePoint({self._x}, {self._y})"
p = ImmutablePoint(10, 20)
print(f"Point: {p}")
print(f"x={p.x}, y={p.y}")
# Cannot modify
try:
p.x = 30
except AttributeError as e:
print(f"Error: {e}")
# Cannot add attributes
try:
p.z = 40
except AttributeError as e:
print(f"Error: {e}")
slots_inheritance.py
"""__slots__ with inheritance"""
# Basic inheritance
print("Basic inheritance:")
class Base:
__slots__ = ['x']
def __init__(self, x):
self.x = x
class Derived(Base):
__slots__ = ['y'] # Only add new slots
def __init__(self, x, y):
super().__init__(x)
self.y = y
obj = Derived(10, 20)
print(f"x={obj.x}, y={obj.y}")
print(f"Base __slots__: {Base.__slots__}")
print(f"Derived __slots__: {Derived.__slots__}")
# Combined slots are inherited
try:
obj.z = 30
except AttributeError as e:
print(f"Error: {e}")
# Empty slots in derived
print("\nEmpty slots in derived:")
class BaseClass:
__slots__ = ['a', 'b']
def __init__(self, a, b):
self.a = a
self.b = b
class DerivedClass(BaseClass):
__slots__ = [] # No new slots
def method(self):
return self.a + self.b
obj = DerivedClass(5, 10)
print(f"a={obj.a}, b={obj.b}")
print(f"method()={obj.method()}")
# Parent without __slots__
print("\nParent without __slots__:")
class ParentNoSlots:
def __init__(self, x):
self.x = x
class ChildWithSlots(ParentNoSlots):
__slots__ = ['y']
def __init__(self, x, y):
super().__init__(x)
self.y = y
obj = ChildWithSlots(10, 20)
# Parent has __dict__, child has slots
print(f"Has __dict__: {hasattr(obj, '__dict__')}")
print(f"__dict__: {obj.__dict__}") # Contains parent attributes
# Can add attributes to parent's __dict__
obj.z = 30
print(f"Added z: {obj.z}")
# But child's slotted attributes are separate
print(f"y (slotted): {obj.y}")
# Child without __slots__
print("\nChild without __slots__:")
class ParentWithSlots:
__slots__ = ['x']
def __init__(self, x):
self.x = x
class ChildNoSlots(ParentWithSlots):
# No __slots__ means __dict__ is created
def __init__(self, x, y):
super().__init__(x)
self.y = y # Stored in __dict__
obj = ChildNoSlots(10, 20)
print(f"x (slotted): {obj.x}")
print(f"y (dict): {obj.y}")
print(f"Has __dict__: {hasattr(obj, '__dict__')}")
# Can add dynamic attributes
obj.z = 30
print(f"Added z: {obj.z}")
# Multiple inheritance
print("\nMultiple inheritance:")
class A:
__slots__ = ['a']
class B(A):
__slots__ = ['b']
class C(B):
__slots__ = ['c']
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
obj = C(1, 2, 3)
print(f"a={obj.a}, b={obj.b}, c={obj.c}")
# Diamond inheritance
print("\nDiamond inheritance:")
class Base2:
__slots__ = ['value']
class Left(Base2):
__slots__ = ['left_value']
# For diamond to work, only one branch can have non-empty __slots__
class Right(Base2):
__slots__ = [] # Empty slots - avoids layout conflict
class Diamond(Left, Right):
__slots__ = ['diamond_value']
def __init__(self, value, left, diamond):
self.value = value
self.left_value = left
self.diamond_value = diamond
obj = Diamond(1, 2, 3)
print(f"value={obj.value}, left={obj.left_value}, diamond={obj.diamond_value}")
# Override with __dict__
print("\nOverride with __dict__:")
class Parent:
__slots__ = ['x']
class Child(Parent):
__slots__ = ['y', '__dict__'] # Add __dict__ explicitly
def __init__(self, x, y):
self.x = x
self.y = y
obj = Child(10, 20)
print(f"x={obj.x}, y={obj.y}")
# Can now add dynamic attributes
obj.z = 30
print(f"Added z: {obj.z}")
print(f"__dict__: {obj.__dict__}")
# Mixin classes
print("\nMixin classes:")
class SlottedBase:
__slots__ = ['id', 'name']
class Mixin:
def get_info(self):
return f"{self.name} (ID: {self.id})"
class ConcreteClass(SlottedBase, Mixin):
__slots__ = ['email']
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
obj = ConcreteClass(1, "Alice", "alice@example.com")
print(f"Info: {obj.get_info()}")
print(f"Email: {obj.email}")
# Practical example
print("\nPractical example:")
# Shape hierarchy
class Shape:
__slots__ = ['color']
def __init__(self, color):
self.color = color
class Circle(Shape):
__slots__ = ['radius']
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
class Rectangle(Shape):
__slots__ = ['width', 'height']
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
circle = Circle("red", 5)
rectangle = Rectangle("blue", 10, 20)
print(f"Circle: color={circle.color}, radius={circle.radius}, area={circle.area():.2f}")
print(f"Rectangle: color={rectangle.color}, width={rectangle.width}, height={rectangle.height}, area={rectangle.area()}")
slots_use_cases.py
"""When to use __slots__"""
# When to use __slots__
print("When to use __slots__:")
# 1. Many instances
class Particle:
__slots__ = ['x', 'y', 'vx', 'vy']
def __init__(self, x, y, vx, vy):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
# Useful when creating thousands/millions
particles = [Particle(i, i*2, i*3, i*4) for i in range(100)]
print(f"✓ Good: Created {len(particles)} particles with __slots__")
# Fixed attributes
print("\nFixed attributes:")
class DatabaseRecord:
__slots__ = ['id', 'timestamp', 'user_id', 'action', 'data']
def __init__(self, id, timestamp, user_id, action, data):
self.id = id
self.timestamp = timestamp
self.user_id = user_id
self.action = action
self.data = data
record = DatabaseRecord(1, 1234567890, 42, "login", {"ip": "127.0.0.1"})
print(f"✓ Good: Fixed schema prevents typos")
# Data classes
print("\nData classes:")
class Point3D:
__slots__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return f"Point3D({self.x}, {self.y}, {self.z})"
def distance_from_origin(self):
return (self.x**2 + self.y**2 + self.z**2) ** 0.5
p = Point3D(3, 4, 5)
print(f"✓ Good: Simple data container")
print(f" Point: {p}, distance: {p.distance_from_origin():.2f}")
# When NOT to use __slots__
print("\nWhen NOT to use __slots__:")
# 1. Dynamic attributes needed
class FlexibleConfig:
# Don't use __slots__ here
def __init__(self):
self.settings = {}
def set(self, key, value):
self.settings[key] = value
config = FlexibleConfig()
config.debug = True # Need this flexibility
config.timeout = 30
print(f"✗ Bad for __slots__: Dynamic attributes needed")
# Few instances
print("\nFew instances:")
# Don't use __slots__ for singletons or few instances
class AppConfig:
# No need for __slots__ - only one instance
def __init__(self):
self.host = "localhost"
self.port = 8080
self.debug = False
config = AppConfig()
print(f"✗ Bad for __slots__: Only a few instances")
# Complex inheritance
print("\nComplex inheritance:")
# Avoid __slots__ with complex inheritance hierarchies
class Base:
def __init__(self):
self.base_attr = "base"
class Mixin1:
def method1(self):
return "mixin1"
class Mixin2:
def method2(self):
return "mixin2"
class ComplexClass(Base, Mixin1, Mixin2):
# Don't add __slots__ here - too complex
def __init__(self):
super().__init__()
self.extra = "extra"
obj = ComplexClass()
print(f"✗ Bad for __slots__: Complex inheritance")
# Performance-critical code
print("\nPerformance-critical code:")
class Vector:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def dot(self, other):
return self.x * other.x + self.y * other.y
# Good for tight loops
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(f"✓ Good: Performance-critical math operations")
# Large collections
print("\nLarge collections:")
class LogEntry:
__slots__ = ['timestamp', 'level', 'message']
def __init__(self, timestamp, level, message):
self.timestamp = timestamp
self.level = level
self.message = message
# Good for storing millions of log entries in memory
logs = [LogEntry(i, "INFO", f"Message {i}") for i in range(100)]
print(f"✓ Good: Large collections ({len(logs)} entries)")
# Embedded systems
print("\nEmbedded systems:")
class SensorReading:
__slots__ = ['sensor_id', 'value', 'unit']
def __init__(self, sensor_id, value, unit):
self.sensor_id = sensor_id
self.value = value
self.unit = unit
reading = SensorReading(1, 23.5, "C")
print(f"✓ Good: Memory-constrained environments")
# Game development
print("\nGame development:")
class Entity:
__slots__ = ['id', 'x', 'y', 'sprite', 'health']
def __init__(self, id, x, y, sprite, health):
self.id = id
self.x = x
self.y = y
self.sprite = sprite
self.health = health
# Good for thousands of game entities
entities = [Entity(i, i*10, i*20, f"sprite{i}", 100) for i in range(100)]
print(f"✓ Good: Game with {len(entities)} entities")
# Practical decision guide
print("\nPractical decision guide:")
print("\nUse __slots__ when:")
print(" ✓ Creating many instances (>1000)")
print(" ✓ Attributes are fixed and known")
print(" ✓ Memory is constrained")
print(" ✓ Attribute access speed matters")
print(" ✓ Preventing attribute typos is valuable")
print("\nAvoid __slots__ when:")
print(" ✗ Dynamic attributes are needed")
print(" ✗ Only a few instances exist")
print(" ✗ Complex inheritance hierarchies")
print(" ✗ Need __dict__ for introspection")
print(" ✗ Using libraries that expect __dict__")
# Real-world example
print("\nReal-world example:")
# Time series data storage
class TimeSeriesPoint:
__slots__ = ['timestamp', 'value']
def __init__(self, timestamp, value):
self.timestamp = timestamp
self.value = value
# Simulate 1 day of per-second data
seconds_per_day = 86400
timeseries = [TimeSeriesPoint(i, i * 0.1) for i in range(100)] # Sample
print(f"✓ Excellent use case: Time series with {len(timeseries)} data points")
print(f" Each point: timestamp + value")
print(f" Fixed schema, many instances, memory-efficient")
The decision comes down to: Do you have many instances? Are attributes fixed and known? Is memory or access speed a concern?
Benefits
- Memory savings: ~40-50% reduction for simple classes
- Faster attribute access: No dict lookup overhead
- Type safety: Prevents typos in attribute names
Limitations
- No dynamic attributes after instantiation
- Inheritance requires care (all classes need
__slots__) - No
__dict__by default (breaks some libraries) - Cannot use with multiple inheritance from slotted classes with non-empty slots
slots declaration
Defining `__slots__` as a list or tuple of attribute names to specify the only attributes instances can have.
memory optimization
The primary benefit of `__slots__` is reducing per-instance memory by ~40-50% for simple classes by eliminating the `__dict__` overhead.
attribute restriction
With `__slots__`, attempting to add attributes not in the slots list raises an `AttributeError`, preventing typos and enforcing structure.
slots inheritance
Child classes only need to define slots for their new attributes; they inherit parent slots automatically.
slots decision
Use `__slots__` when creating many instances of simple classes; avoid it when you need dynamic attributes or have complex inheritance.
Exercise: slots_practice.py
Create a Point class with __slots__ and compare its memory usage to a regular class