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

Для чего используется __slots__ в Python?

1.8 Middle🔥 101 комментариев
#Python Core

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

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

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

slots в Python

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

Зачем нужен slots

  1. Экономия памяти — уменьшает использование памяти на 40-50%
  2. Небольшое ускорение — быстрее доступ к атрибутам
  3. Защита от опечаток — нельзя добавлять новые атрибуты
  4. Контроль интерфейса — явно определяешь, какие атрибуты может иметь объект

Как работает без slots

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

person = Person("Alice", 30)

# Каждый объект имеет словарь __dict__ для хранения атрибутов
print(person.__dict__)  # {'name': 'Alice', 'age': 30}

# Можно добавить новый атрибут в любой момент
person.email = "alice@example.com"
print(person.__dict__)  # {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}

# Это требует дополнительной памяти для каждого объекта
import sys
print(sys.getsizeof(person.__dict__))  # 280 байт на словарь

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

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

person = Person("Alice", 30)

# Объект больше не имеет __dict__
print(hasattr(person, '__dict__'))  # False

# Можно установить определённые атрибуты
person.email = "alice@example.com"
print(person.name)   # Alice
print(person.email)  # alice@example.com

# Но НЕЛЬЗЯ добавлять новые атрибуты
person.phone = "123-456-7890"  # AttributeError: 'Person' object has no attribute 'phone'

Сравнение памяти

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

# Сравниваем размер
with_dict = PersonWithDict("Alice", 30)
with_slots = PersonWithSlots("Alice", 30)

print(f"С __dict__: {sys.getsizeof(with_dict)} байт")
print(f"Со __slots__: {sys.getsizeof(with_slots)} байт")

# Если создать миллион объектов:
objects_dict = [PersonWithDict(f"User{i}", i) for i in range(1000000)]
objects_slots = [PersonWithSlots(f"User{i}", i) for i in range(1000000)]

import gc
gc.collect()

# __slots__ экономит десятки МБ памяти
print(sys.getsizeof(objects_dict))  # ~70+ МБ
print(sys.getsizeof(objects_slots)) # ~30+ МБ

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

class Animal:
    __slots__ = ['name']

class Dog(Animal):
    __slots__ = ['breed']  # Только новые атрибуты
    
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

dog = Dog("Rex", "Labrador")
print(dog.name)    # Rex
print(dog.breed)   # Labrador

# ВАЖНО: если родитель не использует __slots__, потомок будет иметь __dict__
class Animal:
    pass

class Dog(Animal):
    __slots__ = ['breed']  # Это НЕ сэкономит память

dog = Dog()
dog.name = "Rex"  # Это сработает, несмотря на __slots__

Ограничения slots

class Person:
    __slots__ = ['name', 'age']

person = Person()

# 1. Нельзя присваивать __dict__
person.__dict__ = {}  # AttributeError

# 2. Нельзя использовать слабые ссылки (weak references)
# import weakref
# weakref.ref(person)  # TypeError

# 3. Нельзя добавлять методы класса динамически
Person.new_method = lambda self: None  # Это сработает, но приведёт к проблемам

# 4. Сложнее при множественном наследовании
class Mixin:
    __slots__ = ['x']

class Base:
    __slots__ = ['y']

class Derived(Mixin, Base):  # Может быть сложнее
    __slots__ = ['z']

slots и производительность

import timeit

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

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

# Доступ к атрибутам немного быстрее
time_dict = timeit.timeit(lambda: WithDict(1).x, number=1000000)
time_slots = timeit.timeit(lambda: WithSlots(1).x, number=1000000)

print(f"__dict__: {time_dict}")
print(f"__slots__: {time_slots}")
# __slots__ примерно на 10-20% быстрее

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

Используй slots, если:

  • Создаёшь большое количество объектов (тысячи и миллионы)
  • Память критична (встраиваемые системы, мобильные приложения)
  • Атрибуты фиксированы и не меняются
  • Нужна защита от опечаток
class Point:
    __slots__ = ['x', 'y', 'z']
    
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

# Создание миллионов точек для 3D графики
points = [Point(i, i+1, i+2) for i in range(1000000)]

НЕ используй slots, если:

  • Нужна гибкость добавления новых атрибутов
  • Используешь множественное наследование
  • Нужны слабые ссылки
  • Объектов немного (экономия памяти не критична)

Практический пример: ORM модель

class DBModel:
    __slots__ = ['id', 'name', 'email', 'created_at']
    
    def __init__(self, id, name, email, created_at):
        self.id = id
        self.name = name
        self.email = email
        self.created_at = created_at

# Если в БД миллионы записей, это значительно сэкономит память
records = [DBModel(i, f"User{i}", f"user{i}@example.com", None) for i in range(1000000)]

Выводы

slots — это инструмент оптимизации памяти и производительности:

  • Экономит 40-50% памяти
  • Немного ускоряет доступ к атрибутам
  • Защищает от случайных опечаток
  • Имеет ограничения и усложняет наследование

Используй slots в специфических случаях, когда это действительно необходимо. В большинстве приложений это не требуется.