← Назад к вопросам
Как меняется поведение класса после определения __slots__?
2.2 Middle🔥 111 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как меняется поведение класса после определения slots
__slots__ — это мощный механизм оптимизации памяти в Python, который кардинально меняет поведение класса. Он ограничивает атрибуты, которые может иметь объект, и исключает использование словаря __dict__.
Без slots (стандартное поведение)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(person.__dict__) # {'name': 'Alice', 'age': 30}
# Можно добавить новый атрибут в любой момент
person.city = "Moscow"
print(person.__dict__) # {'name': 'Alice', 'age': 30, 'city': 'Moscow'}
# Объект имеет словарь
print(object.__sizeof__(person)) # ~440 байт (зависит от платформы)
С slots (оптимизированное поведение)
class PersonOptimized:
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
person = PersonOptimized("Alice", 30)
# НЕТ __dict__!
try:
print(person.__dict__)
except AttributeError:
print("AttributeError: PersonOptimized object has no attribute '__dict__'")
# Нельзя добавить новый атрибут
try:
person.city = "Moscow"
except AttributeError:
print("AttributeError: 'PersonOptimized' object has no attribute 'city'")
# Объект меньше в памяти
print(object.__sizeof__(person)) # ~56 байт (примерно в 8 раз меньше)
Основные изменения поведения
1. Нет динамического добавления атрибутов
class Student:
__slots__ = ['name', 'gpa']
def __init__(self, name):
self.name = name
self.gpa = 4.0
student = Student("Bob")
# Работает
student.name = "Robert"
student.gpa = 3.5
# НЕ работает — ошибка
try:
student.email = "bob@example.com"
except AttributeError as e:
print(f"Ошибка: {e}")
# Ошибка: 'Student' object has no attribute 'email'
2. Нет доступа к dict
class Point:
__slots__ = ['x', 'y']
p = Point()
p.x = 10
p.y = 20
# НЕЛЬЗЯ итерировать по атрибутам через __dict__
try:
for key, value in p.__dict__.items():
print(key, value)
except AttributeError:
print("Доступ к __dict__ невозможен")
# НУЖНО использовать __slots__
for slot in p.__slots__:
print(f"{slot}: {getattr(p, slot)}")
# x: 10
# y: 20
3. Сохранение памяти
import sys
class RegularClass:
pass
class SlottedClass:
__slots__ = ['value']
# Создаём 100000 объектов
regular_objects = [RegularClass() for _ in range(100000)]
slotted_objects = [SlottedClass() for _ in range(100000)]
# Примерное использование памяти
regular_memory = sum(sys.getsizeof(obj) for obj in regular_objects)
slotted_memory = sum(sys.getsizeof(obj) for obj in slotted_objects)
print(f"Regular: {regular_memory / 1024 / 1024:.2f} MB")
print(f"Slotted: {slotted_memory / 1024 / 1024:.2f} MB")
print(f"Сокращение: {(1 - slotted_memory/regular_memory)*100:.1f}%")
# Вывод:
# Regular: 12.45 MB
# Slotted: 2.67 MB
# Сокращение: 78.5%
4. Ускорение доступа к атрибутам
import timeit
class RegularClass:
def __init__(self):
self.value = 0
class SlottedClass:
__slots__ = ['value']
def __init__(self):
self.value = 0
# Тест скорости доступа
regular = RegularClass()
slotted = SlottedClass()
regular_time = timeit.timeit(lambda: regular.value, number=1000000)
slotted_time = timeit.timeit(lambda: slotted.value, number=1000000)
print(f"Regular: {regular_time:.4f}s")
print(f"Slotted: {slotted_time:.4f}s")
print(f"Ускорение: {regular_time/slotted_time:.1f}x")
# Вывод:
# Regular: 0.1234s
# Slotted: 0.0856s
# Ускорение: 1.4x
Работа с наследованием
class Base:
__slots__ = ['x']
class Derived(Base):
__slots__ = ['y']
obj = Derived()
obj.x = 10
obj.y = 20
print(f"x: {obj.x}, y: {obj.y}")
# НЕ забудь определить __slots__ в подклассе!
class BadDerived(Base):
pass # Нет __slots__ в подклассе
# BadDerived БУДЕТ иметь __dict__!
bad = BadDerived()
bad.x = 10
bad.z = 30 # Это работает!
print(bad.__dict__) # {'z': 30}
Проблема: слабые ссылки
import weakref
class NoSlots:
pass
class WithSlots:
__slots__ = ['value']
obj1 = NoSlots()
obj2 = WithSlots()
# Со слабыми ссылками работает
ref1 = weakref.ref(obj1)
print(ref1()) # <__main__.NoSlots object at 0x...>
# С __slots__ ошибка
try:
ref2 = weakref.ref(obj2)
except TypeError as e:
print(f"Ошибка: {e}")
# Ошибка: cannot create 'weakref' to 'WithSlots' objects
# Решение: добавить __weakref__ в __slots__
class WithSlotsAndWeakref:
__slots__ = ['value', '__weakref__']
obj3 = WithSlotsAndWeakref()
ref3 = weakref.ref(obj3)
print(ref3()) # Работает!
Практические примеры
Пример 1: Модель данных с slots
class User:
__slots__ = ['id', 'name', 'email', 'age']
def __init__(self, id, name, email, age):
self.id = id
self.name = name
self.email = email
self.age = age
def __repr__(self):
return f"User({self.id}, {self.name}, {self.email}, {self.age})"
user = User(1, "Alice", "alice@example.com", 30)
print(user) # User(1, Alice, alice@example.com, 30)
Пример 2: Оптимизация для высоконагруженной системы
class Event:
"""Событие в системе обработки событий"""
__slots__ = ['timestamp', 'event_type', 'user_id', 'data']
def __init__(self, timestamp, event_type, user_id, data):
self.timestamp = timestamp
self.event_type = event_type
self.user_id = user_id
self.data = data
# Миллионы событий в памяти — __slots__ экономит GB памяти
events = [Event(i, 'click', i % 1000, {}) for i in range(1_000_000)]
Пример 3: Правильное использование с методами
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
def distance_from_origin(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
@property
def magnitude(self):
return self.distance_from_origin()
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p.magnitude) # 5.0
Когда использовать slots
Использут slots:
- Когда нужна оптимизация памяти (большое количество объектов)
- Когда требуется улучшить производительность доступа к атрибутам
- Когда атрибуты известны и неизменны
- В моделях данных, которые часто создаются
НЕ используй slots:
- Когда нужно добавлять атрибуты динамически
- Когда нужны слабые ссылки (без
__weakref__) - Когда используются инструменты, ожидающие
__dict__(некоторые ORM, pickle) - В простых классах, где производительность не критична
Распространённые ошибки
# ОШИБКА 1: забыли __weakref__
class Bad:
__slots__ = ['x']
# Слабые ссылки не будут работать
# ОШИБКА 2: забыли __slots__ в подклассе
class Base:
__slots__ = ['x']
class Derived(Base):
pass # Теперь Derived имеет __dict__!
# ОШИБКА 3: пытаются сериализовать с pickle (некоторые версии)
class WithSlots:
__slots__ = ['x']
# pickle может не сработать без правильной конфигурации
# ПРАВИЛЬНО
class Good:
__slots__ = ['x', '__weakref__']
class GoodDerived(Good):
__slots__ = ['y']
Вывод
slots кардинально меняет поведение класса:
- Нет динамических атрибутов — только те, что определены
- Нет dict — экономит память
- Быстрее — оптимизированный доступ
- Требует планирования — не забыть все атрибуты заранее
- Усложняет интроспекцию — нельзя просто итерировать по атрибутам
Используй slots для оптимизации, но помни о цене в гибкости.