В классе объявили __slots__ без __dict__: где Python ожидает хранить атрибуты экземпляра?
При наличии __slots__ без __dict__ атрибуты экземпляра хранятся не в обычном словаре экземпляра, а в слотах, описанных классом. Произвольное добавление атрибута, отсутствующего среди этих слотов, завершается ошибкой AttributeError.
Обычный экземпляр Python-класса обычно хранит атрибуты в __dict__. Это удобно: объект можно расширять во время выполнения, но словарь требует дополнительной памяти и допускает случайные имена атрибутов.
__slots__ появился как механизм явного описания набора экземплярных атрибутов. Он позволяет отказаться от словаря экземпляра, уменьшить накладные расходы для большого числа однотипных объектов и раньше обнаруживать ошибки в именах атрибутов.
Если класс создаёт миллионы небольших объектов с фиксированным набором данных, отдельный словарь у каждого экземпляра может быть неоправданно дорогим. С другой стороны, код или библиотека могут рассчитывать на динамическое добавление атрибутов, сериализацию через __dict__ или использование слабых ссылок.
Неверный выбор приводит либо к лишнему потреблению памяти, либо к ошибкам выполнения в местах, где ранее можно было присвоить новый атрибут. Поэтому __slots__ — это не просто оптимизация, а изменение модели экземпляра.
Каждое имя в __slots__ создаёт в классе специальный дескриптор хранения. При чтении и записи такого атрибута Python обращается к этому дескриптору, а значение размещается в слоте экземпляра.
Если __slots__ не содержит __dict__, у экземпляра нет обычного словаря атрибутов. Поэтому присваивание имени, которого нет среди слотов, невозможно:
Здесь x и y разрешены, а color — нет. Это ограничение действует на конкретный экземпляр, но не означает, что класс полностью неизменяем: атрибуты класса по-прежнему можно изменять, если это допускает код.
Если добавить __dict__ в __slots__, экземпляр снова сможет хранить произвольные атрибуты, однако преимущество отказа от словаря теряется. Аналогично, для поддержки слабых ссылок обычно требуется явно предусмотреть слот __weakref__.
При наследовании нужно анализировать всю иерархию. Подкласс, в котором __slots__ не объявлен, обычно получает собственный __dict__, даже если базовый класс использует слоты. Поэтому контроль набора атрибутов может быть нарушен в подклассе.
__slots__ не делает объект неизменяемым и не заменяет валидацию значений. Он ограничивает главным образом набор мест хранения атрибутов; значения слотов можно переassignивать, если дополнительно не установлен дескриптор или свойство с другими правилами.
В сервисе создаются миллионы объектов координат, у каждого строго фиксированы три числовых поля. Вариант с обычным классом прост и совместим с динамическими атрибутами, но каждый экземпляр несёт накладные расходы словаря. Вариант с __slots__ уменьшает модель хранения и предотвращает опечатки в именах полей, но требует проверить сериализацию, наследование и инструменты отладки.
Добавление __dict__ сохраняет гибкость, но снижает смысл оптимизации. Выбран класс со слотами без __dict__, потому что схема объекта стабильна, а все места, где требуются дополнительные данные, были вынесены во внешний индекс.
В результате случайные атрибуты стали обнаруживаться сразу при тестировании, а потребление памяти уменьшилось. Перед таким изменением необходимо измерять эффект на целевом Python и конкретном размере объектов, а не считать экономию гарантированной для любого класса.
__slots__ в базовом классе, чтобы подкласс не имел __dict__?Нет. Если подкласс не объявляет собственный __slots__, он обычно получает словарь экземпляра. Чтобы сохранить ограничение, в каждом соответствующем классе иерархии явно объявляют __slots__, часто с пустым набором в подклассах, которым не нужны новые поля.
Это важно при проектировании библиотечного базового класса: один свободный подкласс может вернуть динамические атрибуты всей последующей ветке наследования.
__slots__ для запрета изменения уже существующих атрибутов?Нет. Слот ограничивает наличие и способ хранения атрибута, но обычная запись в него остаётся разрешённой. Если нужно запретить изменение или проверять значение, применяют свойства, дескрипторы, переопределение __setattr__ либо другую явную логику.
Следовательно, __slots__ решает проблему структуры экземпляра, а не проблему неизменяемости или валидации данных.
__dict__ может перестать работать после перехода на __slots__?У экземпляра без слота __dict__ нет атрибута __dict__, поэтому код, который собирает состояние через obj.__dict__, завершится ошибкой или не увидит слотированные поля. Сериализатор должен получать данные через заранее известные имена, специальные методы сериализации или поддержку слотов.
Это один из главных практических компромиссов: более компактное хранение связывает внутреннее представление объекта с контрактом инструментов, работающих с его состоянием.