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

Что такое __slots__?

1.7 Middle🔥 191 комментариев
#Python

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

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

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

slots: Оптимизация памяти в Python

Определение

slots - это механизм в Python для явного определения атрибутов класса и экономии памяти путём исключения dict для каждого объекта.

Как работает память без slots

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

person = Person("Иван", 28)

# У каждого объекта есть __dict__ - словарь всех атрибутов
print(person.__dict__)  # {'name': 'Иван', 'age': 28}

# Словари занимают много памяти (248+ байт за словарь)
# Если создать 1,000,000 объектов, это ~250 MB только на __dict__

Введение slots

class PersonWithSlots:
    __slots__ = ['name', 'age']  # только эти атрибуты разрешены
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

person = PersonWithSlots("Иван", 28)

# Нет __dict__!
print(hasattr(person, '__dict__'))  # False

# Но атрибуты работают:
print(person.name)  # Иван
print(person.age)   # 28

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

import sys

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

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

# Создай по одному объекту
normal = PersonNormal("Иван", 28)
slots = PersonSlots("Иван", 28)

print(f"Нормальный класс: {sys.getsizeof(normal)} байт")
# Выпуск: 344 байт

print(f"Со __slots__: {sys.getsizeof(slots)} байт")
# Выпуск: 48 байт

print(f"Экономия: {344 - 48} байт (86% меньше!)")

Практический пример

import sys
from memory_profiler import profile

# Без __slots__ (памятезатратно)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Со __slots__ (экономно)
class PointSlots:
    __slots__ = ['x', 'y']
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Создай 1,000,000 объектов
normal_points = [Point(i, i+1) for i in range(1_000_000)]
slots_points = [PointSlots(i, i+1) for i in range(1_000_000)]

# Проверь память
print(f"Нормальные Point: {sys.getsizeof(normal_points)}")
print(f"Point со __slots__: {sys.getsizeof(slots_points)}")

# Примерные результаты:
# Нормальные: ~320-350 МБ
# __slots__: ~48-80 МБ (в 4-5 раз меньше!)

Какие операции блокируются slots?

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

person = Person("Иван", 28)

# Работает
print(person.name)  # OK
person.age = 29     # OK

# НЕ работает - динамическое добавление атрибутов
person.email = "ivan@example.com"  # AttributeError!

# НЕ работает - нет __dict__
print(person.__dict__)  # AttributeError!

# НЕ работает - нельзя удалить из __slots__
del person.name  # AttributeError!

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

# Родительский класс
class Animal:
    __slots__ = ['name']

# Дочерний класс - нужно добавить свои __slots__
class Dog(Animal):
    __slots__ = ['breed']  # добавляем новые
    
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

dog = Dog("Шарик", "Овчарка")
print(dog.name)    # OK
print(dog.breed)   # OK

# Если забыть __slots__ в наследнике
class Cat(Animal):
    # Нет __slots__!
    def __init__(self, name, color):
        self.name = name
        self.color = color

cat = Cat("Мурзик", "чёрный")
print(cat.__dict__)  # {'color': 'чёрный'} - есть __dict__!
cat.new_attr = 123  # Работает, можно добавить

Методы и свойства

class Person:
    __slots__ = ['_name', '_age']
    
    def __init__(self, name, age):
        self._name = name
        self._age = age
    
    # Свойства (properties) работают со __slots__
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Имя не может быть пустым")
        self._name = value
    
    def birthday(self):
        self._age += 1

person = Person("Иван", 28)
print(person.name)     # OK
person.name = "Петр"   # OK
person.birthday()      # OK

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

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

  1. Много объектов (миллионы экземпляров)

    # Пример: приложение обрабатывает миллионы пользователей
    users = [User(...) for _ in range(10_000_000)]
    
  2. Критична память (встроенные системы, серверы)

    # IoT устройство с ограниченной памятью
    # каждый MB важен
    
  3. Стабильное множество атрибутов

    # Класс Data Point с фиксированными полями
    class DataPoint:
        __slots__ = ['timestamp', 'value', 'sensor_id']
    
  4. Хочешь избежать случайных опечаток

    # Без __slots__ можно случайно создать новый атрибут
    person.nme = "Иван"  # опечатка, но работает
    
    # Со __slots__ ловится ошибка
    # person.nme = "Иван"  # AttributeError!
    

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

  1. Мало объектов (десятки, сотни)
  2. Нужна гибкость (добавлять новые атрибуты)
  3. Используешь множественное наследование (может быть проблемы)
  4. Нужен JSON сериализация через dict

Практический пример в ML

import numpy as np
from typing import List

# Класс для хранения признаков (features) датасета
class Feature:
    __slots__ = ['name', 'values', 'dtype']
    
    def __init__(self, name: str, values: np.ndarray):
        self.name = name
        self.values = values
        self.dtype = values.dtype
    
    def normalize(self):
        mean = np.mean(self.values)
        std = np.std(self.values)
        self.values = (self.values - mean) / std

# Создай 1000 признаков для большого датасета
features = [
    Feature(f"feature_{i}", np.random.randn(1000))
    for i in range(1000)
]

# Со __slots__ это занимает ~2x меньше памяти
print(f"Всего объектов: {len(features)}")
print(f"Каждый объект: {48} байт (со __slots__)")
print(f"Всего: ~{len(features) * 48 / 1024 / 1024:.1f} МБ")

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

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

2. Нельзя использовать __weakref__ по умолчанию
   # Нужно добавить:
   __slots__ = ['name', '__weakref__']

3. Сложнее с множественным наследованием
   class A:
       __slots__ = ['a']
   class B:
       __slots__ = ['b']
   class C(A, B):  # Проблема!
       pass

4. Pickle и сериализация могут быть сложнее

Отладка

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

person = Person()
person.name = "Иван"

# Посмотреть доступные атрибуты
print(Person.__slots__)  # ['name', 'age']

# Проверить, есть ли атрибут
print(hasattr(person, 'name'))  # True
print(hasattr(person, 'email'))  # False

# Список всех слотов класса
for cls in Person.__mro__:  # Method Resolution Order
    if hasattr(cls, '__slots__'):
        print(f"{cls.__name__}: {cls.__slots__}")

Заключение

slots - это инструмент для оптимизации памяти в Python. Если ты работаешь с большим количеством объектов и память критична, slots может снизить использование памяти в 4-10 раз. Однако это приносит потерю гибкости (нельзя добавлять новые атрибуты). Выбирай slots когда есть реальная потребность в оптимизации памяти, а не профилактически.

Что такое __slots__? | PrepBro