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

Как меняется поведение класса после определения __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 для оптимизации, но помни о цене в гибкости.

Как меняется поведение класса после определения __slots__? | PrepBro