Python dataclass slots: меньше памяти на объект

Категория: Python · Язык: Python · Обновлено:

@dataclass(slots=True) добавляет классу __slots__ и убирает у каждого экземпляра словарь __dict__. Для тысяч небольших объектов это экономит память и немного ускоряет доступ к полям. Параметр появился в Python 3.10; на 3.9 и раньше __slots__ приходилось описывать вручную.

Создать dataclass без словаря

Python
from dataclasses import dataclass

@dataclass(slots=True)
class Point:
    x: int
    y: int

point = Point(10, 20)
print(point)                        # Point(x=10, y=20)
print(hasattr(point, "__dict__"))   # False

try:
    point.z = 30
except AttributeError as exc:
    print(type(exc).__name__)       # AttributeError

Экземпляр хранит только объявленные поля, поэтому новое поле нельзя добавить на лету. Это не только экономит память, но и ловит опечатки в именах атрибутов. Если объекту нужны динамические свойства, slots=True не подходит — используйте обычный dataclass или словарь.

Сравнить размер и наследование

Python
from dataclasses import dataclass
import sys

@dataclass
class Regular:
    x: int
    y: int

@dataclass(slots=True)
class Slotted:
    x: int
    y: int

print(hasattr(Regular(1, 2), "__dict__"))  # True
print(hasattr(Slotted(1, 2), "__dict__"))  # False
print(sys.getsizeof(Regular(1, 2)) >= sys.getsizeof(Slotted(1, 2)))  # True

sys.getsizeof() показывает размер самого объекта без содержимого словаря, поэтому реальная экономия выше. Для точного замера используйте tracemalloc или pympler.asizeof.

Если базовый класс не имеет __slots__, экземпляр всё равно получит __dict__, и часть экономии потеряется. В иерархии объявляйте слоты на всех уровнях. Если поле уже есть в __slots__ базового класса, начиная с Python 3.11 dataclass не добавит его повторно, поэтому не используйте __slots__ для перечисления полей — берите dataclasses.fields(). Для слабых ссылок добавьте weakref_slot=True (Python 3.11+), иначе weakref.ref(point) не сработает.

slots=True возвращает новый класс, а не меняет исходный. Документация предупреждает, что передача параметров в __init_subclass__ базового класса в этом режиме может дать TypeError; метаклассы и библиотеки, завязанные на __dict__, тоже стоит проверить тестами. Если нужна максимальная совместимость, объявите __slots__ вручную или оставьте обычный dataclass.

Документация: dataclasses, slots.