← Назад к вопросам

Что дает использование метода slots?

1.3 Junior🔥 81 комментариев
#FastAPI и Flask#Асинхронность и многопоточность#Брокеры сообщений

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование slots в Python

Что такое slots

slots — это специальный атрибут класса, который ограничивает набор атрибутов экземпляра. Вместо использования словаря __dict__ для хранения атрибутов, Python использует более эффективное хранилище.

Проблема без slots

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

p = Point(1, 2)
print(p.__dict__)  # {'x': 1, 'y': 2}

# Но ты можешь добавить любой атрибут!
p.z = 3
p.color = "red"
p.__dict__  # {'x': 1, 'y': 2, 'z': 3, 'color': 'red'}

# Это требует дополнительную память для словаря

Решение со slots

class Point:
    __slots__ = ['x', 'y']
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p.x)  # 1

# Теперь этого нельзя сделать!
p.z = 3  # AttributeError: 'Point' object has no attribute 'z'
p.color = "red"  # AttributeError

Основные преимущества slots

1. Экономия памяти (главное преимущество)

import sys

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

class PointWithSlots:
    __slots__ = ['x', 'y']
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

point_dict = PointWithDict(1, 2)
point_slots = PointWithSlots(1, 2)

print(f"Without slots: {sys.getsizeof(point_dict.__dict__)} bytes")
# Без slots: ~240 байт (словарь + накладные расходы)

print(f"With slots: примерно 56 байт")
# Со slots: примерно 56 байт (только сами значения)

# Разница не критична для одного объекта,
# но если создаёшь миллионы объектов:
points_dict = [PointWithDict(i, i) for i in range(1_000_000)]
points_slots = [PointWithSlots(i, i) for i in range(1_000_000)]

# Memory usage для dict версии: ~350+ МБ
# Memory usage для slots версии: ~150+ МБ
# Разница в 2+ раза!

2. Немного быстрее доступ к атрибутам

import timeit

class WithDict:
    def __init__(self):
        self.x = 1

class WithSlots:
    __slots__ = ['x']
    def __init__(self):
        self.x = 1

obj_dict = WithDict()
obj_slots = WithSlots()

# Доступ со словарём (медленнее)
time_dict = timeit.timeit(lambda: obj_dict.x, number=10_000_000)

# Доступ со slots (быстрее)
time_slots = timeit.timeit(lambda: obj_slots.x, number=10_000_000)

print(f"Dict: {time_dict:.3f}s")
print(f"Slots: {time_slots:.3f}s")
# Slots примерно на 10-20% быстрее

3. Предотвращение ошибок (контроль атрибутов)

class User:
    __slots__ = ['name', 'email', 'age']
    
    def __init__(self, name, email, age):
        self.name = name
        self.email = email
        self.age = age

user = User("Alice", "alice@example.com", 30)

# Защита от опечаток
user.emial = "typo@example.com"  # AttributeError! Спасает от багов

# Без slots это было бы молча
class UserNoSlots:
    def __init__(self, name, email, age):
        self.name = name
        self.email = email
        self.age = age

user2 = UserNoSlots("Bob", "bob@example.com", 25)
user2.emial = "typo@example.com"  # Молча создаёт новый атрибут
print(user2.__dict__)  # {'name': 'Bob', 'email': '...', 'age': 25, 'emial': '...'}

Когда использовать slots

1. Когда создаёшь много объектов

# Например, обработка больших наборов данных
class DataPoint:
    __slots__ = ['timestamp', 'value', 'status']

data_points = [DataPoint() for _ in range(100_000_000)]  # Сбережет память

2. Для моделей данных

class Product:
    __slots__ = ['id', 'name', 'price', 'quantity']

class Transaction:
    __slots__ = ['id', 'product_id', 'amount', 'date']

3. В критичных по памяти приложениях

# Встроенные системы, обработка миллионов событий
class Event:
    __slots__ = ['event_type', 'timestamp', 'data']

Когда НЕ использовать slots

1. Когда много наследования

# ❌ Проблема
class Base:
    __slots__ = ['x']

class Derived(Base):
    __slots__ = ['y']  # Нужно повторять

class DoublyDerived(Derived):
    __slots__ = ['z']  # И ещё повторять

# Становится сложно

2. Когда нужна гибкость

# ❌ Плохо использовать slots здесь
class Config:
    __slots__ = ['...']  # Нельзя добавлять новые параметры динамически

config = Config()
config.new_param = "value"  # AttributeError!

# ✅ Лучше без slots для конфигов
class Config:
    pass

config = Config()
config.new_param = "value"  # OK

3. Когда используешь __dict__ напрямую

# ❌ Не будет работать
class WithSlots:
    __slots__ = ['x']

obj = WithSlots()
obj.__dict__  # AttributeError: 'WithSlots' object has no attribute '__dict__'

Детали реализации

Наследование и slots

class Base:
    __slots__ = ['x']

class Derived(Base):
    __slots__ = ['y']  # Добавляет y, x наследуется от Base

d = Derived()
d.x = 1
d.y = 2
print(d.x, d.y)  # 1 2

Обход ограничений slots

class WithSlots:
    __slots__ = ['x', '__dict__']  # Добавляем __dict__ явно!

obj = WithSlots()
obj.x = 1
obj.y = 2  # OK! Теперь можем добавлять атрибуты
print(obj.__dict__)  # {'y': 2}

# Но тогда теряется смысл slots для памяти

Практический пример: обработка логов

class LogEntry:
    __slots__ = ['timestamp', 'level', 'message', 'source']
    
    def __init__(self, timestamp, level, message, source):
        self.timestamp = timestamp
        self.level = level
        self.message = message
        self.source = source

# Обработка 10 млн логов
log_entries = [
    LogEntry(time.time(), 'INFO', f'Message {i}', 'app')
    for i in range(10_000_000)
]

# Со slots: примерно 2 GB памяти
# Без slots: примерно 5+ GB памяти

Лучшие практики

1. Используй slots для data-heavy классов

# ✅ Хорошо
class DataFrame:
    __slots__ = ['data', 'columns', 'index']

2. Не переусложняй для простых случаев

# ❌ Избыточно
class SimpleClass:
    __slots__ = ['value']  # Если создаёшь 10 экземпляров, смысла нет

3. Документируй slots

class Model:
    """Модель с фиксированным набором атрибутов."""
    __slots__ = ['id', 'name', 'email']
    # Атрибуты: id (int), name (str), email (str)

Заключение

slots даёт три основных преимущества:

  1. Экономия памяти (в 2-3 раза для больших объёмов)
  2. Небольшое ускорение доступа (5-15%)
  3. Защита от ошибок (предотвращение опечаток)

Используй slots когда:

  • Создаёшь миллионы объектов одного класса
  • Память — критичный ресурс
  • Хочешь зафиксировать набор атрибутов

Не используй slots когда:

  • Нужна динамическая гибкость
  • Сложное наследование
  • Есть сомнения в производительности (профилируй!)