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

Что такое слоты (__slots__) и зачем они нужны?

2.0 Middle🔥 141 комментариев
#Python Core

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

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

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

Слоты (slots) в Python

Определение

__slots__ — это специальный атрибут класса, который ограничивает набор атрибутов, которые могут быть добавлены к экземпляру класса. Это механизм оптимизации памяти и производительности.

Как это работает

По умолчанию, Python хранит атрибуты экземпляра в словаре __dict__. Словари занимают значительное количество памяти, особенно когда у вас много объектов.

# БЕЗ __slots__
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(p.__dict__)  # {'name': 'Alice', 'age': 30}

# Можно добавить любой атрибут
p.email = "alice@example.com"  # Работает!

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

Когда вы определяете __slots__, Python создаёт дескрипторы для каждого атрибута вместо словаря:

class Person:
    __slots__ = ('name', 'age')
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(hasattr(p, '__dict__'))  # False

# Попытка добавить новый атрибут — ошибка!
p.email = "alice@example.com"  
# AttributeError: Person object has no attribute email

Преимущества

1. Экономия памяти

Использование дескрипторов вместо словаря снижает потребление памяти на 40-50%:

import sys

class PersonWithDict:
    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

p1 = PersonWithDict("Alice", 30)
p2 = PersonWithSlots("Bob", 25)

print(sys.getsizeof(p1.__dict__))  # 240 байт
print(sys.getsizeof(p2))            # 56 байт

2. Быстрая обработка атрибутов

Доступ к атрибутам через дескрипторы быстрее, чем поиск в словаре:

import timeit

setup_dict = '''class P:
    def __init__(self):
        self.x = 1
p = P()'''

setup_slots = '''class P:
    __slots__ = ('x',)
    def __init__(self):
        self.x = 1
p = P()'''

time_dict = timeit.timeit('p.x', setup=setup_dict, number=1000000)
time_slots = timeit.timeit('p.x', setup=setup_slots, number=1000000)

print(f"Dict: {time_dict:.4f}s")    # 0.1500s
print(f"Slots: {time_slots:.4f}s")  # 0.0900s

3. Защита от случайного добавления атрибутов

Запрещает динамическое добавление новых полей, что помогает избежать опечаток:

class Config:
    __slots__ = ('debug', 'timeout', 'retries')
    
    def __init__(self):
        self.debug = False
        self.timeout = 30
        self.retries = 3

c = Config()
c.debg = True  # Опечатка!
# AttributeError: Config object has no attribute debg

Недостатки

1. Потеря гибкости

Нельзя динамически добавлять атрибуты (иногда это необходимо).

2. Наследование требует особого подхода

class Parent:
    __slots__ = ('x',)

class Child(Parent):
    __slots__ = ('y',)  # Должна переопределить!
    
# Без переопределения ребёнок будет иметь __dict__:
class BadChild(Parent):
    pass

bc = BadChild()
bc.z = 10  # Работает! Это не то, что мы ожидали

3. Усложнение сериализации

import pickle

class Person:
    __slots__ = ('name',)
    def __init__(self, name):
        self.name = name

p = Person("Alice")
data = pickle.dumps(p)  # Работает, но требует дополнительной работы

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

  • Большое количество объектов (сотни тысяч или миллионы)
  • Критична оптимизация памяти
  • Нужна защита от опечаток в атрибутах
  • Статический набор атрибутов (не меняется)

Когда избегать

  • Нужна динамическое добавление атрибутов
  • Класс активно наследуется
  • Нужна высокая гибкость
  • Профилирование показало, что это не узкое место

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

# Полный пример
class Node:
    __slots__ = ('value', 'left', 'right')
    
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
    
    def __repr__(self):
        return f"Node({self.value})"

# При наследовании
class DataNode(Node):
    __slots__ = ('data',)  # Расширяем, но переопределяем!
    
    def __init__(self, value, data):
        super().__init__(value)
        self.data = data

Вывод

__slots__ — мощный инструмент оптимизации для больших систем с множеством объектов, но требует взвешенного подхода. Используй, только если профилирование показало необходимость.