Внутри переопределённого setattr каким способом сохранить атрибут, не вызвав рекурсивно тот же метод?
Используйте object.setattr(self, имя, значение). Он выполняет стандартную установку атрибута, минуя переопределённый в вашем классе setattr, поэтому рекурсии не возникает.
Прямое присваивание через self.имя внутри setattr снова вызывает этот же метод. Запись в self.dict иногда избегает рекурсии, но обходится без поддержки slots и может нарушить работу дескрипторов.
Модель данных Python позволяет перехватывать стандартные операции над объектами специальными методами. Для присваивания атрибутов таким методом является setattr, что позволяет добавлять валидацию, журналирование, преобразование значений и контроль доступа без изменения синтаксиса кода пользователя.
Одновременно Python предоставляет базовые реализации через object. Это позволяет переопределить поведение только на внешнем уровне, а затем явно передать операцию стандартному механизму.
При вызове присваивания атрибута Python обращается к setattr. Если внутри этой реализации снова выполнить обычное присваивание атрибута через self.имя, Python повторно вызовет setattr. Возникает бесконечная рекурсия и затем исключение RecursionError.
Попытка решить проблему записью в self.dict тоже не универсальна. У экземпляра со слотами может не быть dict, а дескриптор с методом set при прямой записи в словарь вообще не будет вызван.
Надёжный общий способ — вызвать object.setattr(self, имя, значение). Этот вызов обходит переопределение setattr текущего класса, но сохраняет стандартную семантику установки атрибута, включая обработку дескрипторов и объектов со слотами.
В этом примере последняя строка setattr не вызывает саму себя: вызывается реализация object.setattr. Если бы использовалось self.age = value, рекурсия была бы бесконечной.
В приведённом фрагменте значение должно быть обычным целым числом; корректная запись строки с числом выглядит так:
Важно различать два случая. object.setattr сохраняет работу дескрипторов, поэтому это предпочтительный вариант для общего назначения. self.dict[name] = value может быть уместен только при сознательном обходе дескрипторов и наличии словаря экземпляра; это более низкоуровневый и ограниченный приём.
Если в иерархии есть несколько классов, вместо жёсткого вызова object.setattr иногда применяют super().setattr(name, value). Такой вариант продолжает кооперативную цепочку наследования, но его поведение зависит от реализации setattr в классах-предках.
В конфигурационном объекте требуется проверять тип и диапазон значений при каждом присваивании. Рассматривались три варианта: проверять значения в каждом месте использования, реализовать свойства для каждого поля или централизовать проверку в setattr.
Проверки в местах использования приводят к дублированию и позволяют случайно пропустить валидацию. Свойства дают явную типизацию отдельных полей, но при большом количестве динамических параметров создают много шаблонного кода. Централизованный setattr уменьшает дублирование, однако требует аккуратно вызывать object.setattr и учитывать дескрипторы.
Выбран централизованный перехват с передачей операции в object.setattr. Это сохранило поддержку обычных атрибутов и слотов, предотвратило рекурсию и позволило единообразно выдавать ошибки валидации.
Почему вызов object.setattr не означает полного отключения дескрипторов?
object.setattr обходит переопределённый метод текущего класса, но сам является стандартным механизмом установки атрибута. Поэтому при наличии дескриптора класса с set он может вызвать этот дескриптор. Это отличается от прямой записи в dict, которая меняет словарь экземпляра и не запускает дескриптор.
Чем отличается object.setattr от super().setattr в классе-наследнике?
object.setattr напрямую выбирает реализацию базового объекта. super().setattr ищет следующую реализацию по MRO и поэтому поддерживает кооперативное множественное наследование. Если другой базовый класс должен участвовать в установке атрибута, предпочтительнее super(), но при некооперативной иерархии его поведение может отличаться от ожидаемого.
Что произойдёт при использовании setattr у класса со slots без dict?
object.setattr сможет установить атрибут только если для него предусмотрен слот или доступ разрешён дескриптором. Обращение к self.dict завершится AttributeError, поскольку словаря экземпляра нет. Поэтому для классов со слотами особенно важно не подменять стандартную установку прямой записью в dict.