Практическая ситуация: класс использует слоты без словаря экземпляра. Что произойдёт при добавлении экземпляру атрибута, не объявленного среди слотов?
Попытка добавить такой атрибут завершится исключением AttributeError. Экземпляр не имеет обычного атрибута __dict__, поэтому Python не может сохранить имя и значение динамически.
Объявленные слоты не делают объект неизменяемым: значения разрешённых атрибутов по-прежнему можно менять. Ограничивается именно создание атрибутов, отсутствующих в описании класса и его допустимых базовых классов.
Обычный экземпляр Python-класса обычно хранит атрибуты в словаре __dict__. Это удобно: атрибуты можно добавлять во время выполнения, но словарь требует дополнительной памяти и хранит служебную структуру для каждого экземпляра.
Механизм слотов появился как способ заранее описать набор экземплярных атрибутов. Он позволяет уменьшить накладные расходы для большого количества однотипных объектов и одновременно ограничить случайное добавление новых атрибутов.
Представим модель, создаваемую миллионами раз, у которой всегда есть только несколько фиксированных полей. Обычный __dict__ в такой ситуации может занимать заметную долю памяти, а опечатка в имени атрибута создаст новое поле вместо немедленной ошибки.
Слоты решают эти задачи, но лишают код динамичности. Если библиотека или приложение рассчитывает добавлять служебные атрибуты, использовать слабые ссылки или смешивать классы со сложной иерархией наследования, безоговорочный переход на слоты может привести к ошибкам совместимости.
Специальное объявление слотов создаёт для перечисленных имён специальные дескрипторы. Значения таких атрибутов хранятся в заранее предусмотренных местах экземпляра, а не в произвольных записях словаря.
Если среди слотов нет имени __dict__, экземпляр не получает словарь атрибутов. Поэтому обращение к разрешённому имени работает, а присваивание нового имени завершается AttributeError.
Слоты не являются значениями по умолчанию и не инициализируют поля автоматически. Обычно их заполняют в __init__, но технически присваивание разрешённому слоту может быть выполнено позже.
Ограничение действует не абсолютно для всей иерархии. Если базовый класс предоставляет __dict__, экземпляры производного класса обычно сохраняют возможность динамически добавлять атрибуты. Чтобы явно разрешить это в классе со слотами, добавляют __dict__ в список слотов.
Для поддержки слабых ссылок требуется отдельный слот __weakref__, если его не предоставляет базовый класс. При множественном наследовании возможны конфликты layout слотов, поэтому такую иерархию нужно проектировать и тестировать отдельно.
Слоты не защищают от изменения самих значений: список, хранящийся в слоте, остаётся изменяемым. Кроме того, слоты не заменяют проверку инвариантов и не делают объект потокобезопасным.
В сервисе создавались миллионы объектов, каждый из которых имел четыре фиксированных поля. Профилирование показало, что основная память уходила на словари экземпляров, а динамические атрибуты в рабочем процессе не использовались.
Рассматривались два варианта. Сохранение обычного класса не требовало изменений и оставляло максимальную гибкость, но не уменьшало расход памяти. Использование слотов уменьшало накладные расходы и выявляло опечатки в именах атрибутов, но требовало отдельно проверить сериализацию, слабые ссылки и наследников.
Выбрали слоты после измерений и добавили тесты на создание объектов, наследование и интеграционные сценарии. В результате модель стала занимать меньше памяти, а ошибочные динамические поля начали обнаруживаться сразу при тестировании, а не теряться в состоянии объекта.
__slots__ у базового класса, чтобы запретить динамические атрибуты в наследнике?Нет. Если производный класс не объявляет собственный __dict__ и ни один базовый класс не предоставляет его экземплярам, динамические атрибуты останутся запрещены. Однако наличие __dict__ у любого подходящего базового класса обычно возвращает такую возможность всей соответствующей части иерархии.
Поэтому анализировать нужно не только объявление слотов в текущем классе, но и layout базовых классов. Указание слотов в дочернем классе само по себе не гарантирует отсутствие словаря экземпляра.
Не всегда. Слабые ссылки требуют специального места __weakref__. Если ни текущий класс, ни его базовый класс его не предоставляют, создание слабой ссылки завершится TypeError.
Это важно для объектов, которые хранятся в кэшах или реестрах без продления их времени жизни. При проектировании такого класса нужно явно предусмотреть поддержку слабых ссылок либо убедиться, что она уже обеспечивается наследованием.
Нет. Слоты ограничивают способ хранения и набор допустимых экземплярных имён, но не реализуют приватность и неизменяемость. Значение разрешённого слота можно переназначить, если это не запрещено дополнительной логикой, например свойством с ограничением записи.
Также слоты не скрывают атрибуты от интроспекции и не защищают от изменения изменяемого объекта, лежащего в слоте. Для неизменяемой модели нужны отдельные механизмы: свойства, валидация, соглашения API или специальная реализация неизменяемого типа.