За счёт какого механизма __slots__ уменьшает память экземпляров Python-класса?
__slots__ заранее задаёт фиксированный набор атрибутов экземпляра и обычно убирает необходимость в отдельном __dict__ для каждого объекта. Это экономит память за счёт отказа от словаря атрибутов, но ограничивает динамическое добавление полей и требует учитывать особенности наследования, слабых ссылок и сериализации.
Обычный экземпляр Python-класса хранит пользовательские атрибуты через словарь __dict__. Такой подход гибок: атрибуты можно добавлять во время выполнения, а имена полей не обязаны быть заранее известны.
Для классов, создающих очень много однотипных объектов, эта гибкость приводит к заметным накладным расходам. __slots__ появился как механизм описания фиксированной структуры экземпляра, чтобы уменьшить стоимость хранения таких объектов.
Если приложение создаёт миллионы небольших объектов, память расходуется не только на сами значения полей. Дополнительные расходы создают словарь экземпляра, его таблица хеширования и ссылки на значения.
Попытка бездумно добавить __slots__ может сломать код, который динамически добавляет атрибуты, использует __dict__, создаёт слабые ссылки или сериализует объекты через инструменты, ожидающие обычный словарь. Поэтому оптимизацию нужно подтверждать профилированием памяти и проверкой совместимости интерфейса класса.
В обычном классе атрибуты экземпляра обычно находятся в __dict__. При обращении к полю Python ищет его значение в этом словаре, а сам словарь хранит пары «имя атрибута — значение».
При объявлении __slots__ класс получает фиксированные слоты для перечисленных имён. Значения хранятся в структуре экземпляра, связанной с этими слотами, без обязательного отдельного словаря. Поэтому уменьшается размер каждого экземпляра и часто ускоряется доступ к атрибутам за счёт более предсказуемой структуры.
__slots__ не уменьшает память, занятую самими объектами x и y: он сокращает накладные расходы на хранение атрибутов экземпляра. sys.getsizeof следует трактовать осторожно: он не даёт полной стоимости графа объектов и может не включать память, косвенно связанную с атрибутами.
Если класс должен поддерживать динамические атрибуты, в __slots__ можно явно добавить __dict__, но тогда экономия для словаря экземпляра исчезает. Для создания слабых ссылок обычно требуется добавить __weakref__.
При наследовании нужно анализировать всю иерархию. Базовый или дочерний класс с обычным __dict__ может вернуть экземплярам словарь, поэтому локальное объявление __slots__ не гарантирует максимальную экономию во всей структуре наследования.
Существенный компромисс — потеря части динамичности. __slots__ полезен для компактных массовых объектов с фиксированной схемой, но не является универсальной заменой обычным классам и не должен применяться без измерений.
Сервис обрабатывает большой поток событий и хранит в памяти множество однотипных объектов с несколькими фиксированными полями. Профилирование показывает, что значительная доля памяти приходится на накладные расходы экземпляров, а не на сами значения.
Первый вариант — оставить обычный класс. Он проще для отладки, совместим с кодом, использующим __dict__, и допускает добавление новых полей, но сохраняет расходы словаря для каждого объекта.
Второй вариант — перейти на __slots__. Он уменьшает накладные расходы при большом количестве объектов, но требует проверить сериализацию, инструменты отладки, слабые ссылки и сторонний код, который добавляет атрибуты динамически.
Третий вариант — использовать компактную структуру другого типа, например кортеж или специализированный массив. Это может дать ещё меньшие расходы, но ухудшает читаемость, усложняет изменение схемы и не всегда подходит для объекта с поведением.
Выбран вариант с __slots__, поскольку схема события стабильна, динамические атрибуты не нужны, а совместимость проверена тестами. После изменения сравнивают пиковое потребление памяти и время обработки на реалистичном наборе данных, а не делают вывод только по размеру одного экземпляра.
__slots__ отсутствие __dict__ у экземпляра?Нет. Если __dict__ явно указан в __slots__ или доступен через подходящую часть иерархии наследования, экземпляр может иметь словарь. Поэтому наличие __slots__ в одном классе ещё не доказывает, что каждый объект всей иерархии будет храниться без словаря.
__slots__ изменение значений уже объявленных атрибутов?Нет. Объявление слота запрещает неизвестные атрибуты, но разрешает присваивать новые значения слотам, если они не объявлены как свойства только для чтения. Ограничивается набор имён полей, а не обычное изменение их содержимого.
sys.getsizeof двух экземпляров может дать неверный вывод об экономии?sys.getsizeof показывает размер непосредственно измеряемого объекта и не является полным профилем памяти. В случае обычного класса стоимость словаря экземпляра и объектов, на которые указывают атрибуты, может учитываться отдельно или не отражаться так, как ожидает разработчик. Для достоверной оценки нужно измерять множество экземпляров подходящим профилировщиком и учитывать общий граф объектов, жизненный цикл и пиковое потребление памяти.