Как обрабатывать установку значения property с помощью setter в Python
Что проверяют
Понимаете ли вы, как property работает под капотом, а не только синтаксис из двух декораторов. Следом обычно спрашивают, чем свойство отличается от дескриптора и как переопределить в наследнике только сеттер.
Как отвечать
property это дескриптор данных: объект на уровне класса с __get__, __set__ и __delete__. Присваивание obj.x = 5 находит его в классе раньше словаря экземпляра и вызывает сеттер.
- синтаксис:
@propertyнад геттером, затем@x.setterнад функцией с тем же именем.x.setterне дописывает старый объект, а возвращает новую копиюpropertyс сеттером, поэтому имя обязано совпадать; - хранение: значение лежит в атрибуте с подчёркиванием,
self._x. Если в сеттере написатьself.x = value, он вызовет сам себя и упадёт сRecursionError; - валидация: неверный тип даёт
TypeError, недопустимое значениеValueError. Учтите, чтоboolнаследникint, иisinstance(True, int)вернётTrue; - конструктор: в
__init__присваивайте через свойство,self.x = x, тогда проверка сработает и при создании объекта; - без сеттера: свойство только для чтения, присваивание падает с
AttributeError. Для удаления есть@x.deleter; - наследование: чтобы заменить только сеттер, в наследнике пишут
@Base.x.setter, геттер при этом переиспользуется.
Хорошо закончить тем, когда свойство не нужно. Простые атрибуты в Python оставляют публичными, без пары get_x и set_x: свойство можно добавить позже, и вызывающий код этого не заметит. Если одна проверка повторяется во многих полях, её выносят в собственный дескриптор с __set_name__, а для моделей данных берут валидаторы pydantic.
Где спотыкаются
Пишут в __init__ сразу self._x = x. Объект создаётся с невалидным значением, а сеттер проверяет только последующие присваивания.
Называют сеттер иначе, например set_celsius. Класс загружается без ошибок, но celsius остаётся свойством только для чтения, и присваивание падает с AttributeError.
Путают property с functools.cached_property. У того нет __set__, значение кладётся в словарь экземпляра, присваивание его просто перезаписывает, и никакой проверки там нет.