Короткий ответ
property превращает вызов метода в синтаксис обычного доступа к атрибуту (obj.attr вместо obj.get_attr()), позволяя добавить геттер и сеттер с проверками (валидацией, вычислением) без изменения кода, который уже использует obj.attr = value.
Как это работает подробнее
Декоратор @property делает метод геттером, доступным без вызова со скобками. Метод с тем же именем, декорированный @attr.setter, перехватывает присваивание obj.attr = value и может выполнить проверку перед сохранением в "приватное" поле (по конвенции — с подчёркиванием, например self._attr). Если сеттер не определён, property становится доступной только для чтения, и попытка присвоить значение вызывает AttributeError.
Пример кода
example.py
class Product:
def __init__(self, name, price):
self.name = name
self.price = price # пройдёт через сеттер ниже
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Цена не может быть отрицательной")
self._price = value
p = Product("Книга", 500)
p.price = 700 # проходит валидацию прозрачно, как обычное присваивание
print(p.price) # 700
try:
p.price = -10
except ValueError as e:
print("Ошибка:", e)Что использовать на практике
- Практика. property особенно полезна при рефакторинге: класс изначально хранил простое публичное поле, а позже понадобилась валидация или вычисление на основе других полей — property позволяет добавить это без изменения вызывающего кода (
obj.price = 10продолжает работать так же, как раньше, просто теперь проходит проверку).
Подводные камни
- Риски. Делают сеттер, который вызывает побочные эффекты, неочевидные из внешнего вида обычного присваивания (например, сетевой запрос) — это нарушает ожидания читающего код и усложняет отладку. Также забывают, что property объявляется на уровне класса, а не экземпляра, и пытаются добавить property динамически к одному объекту, что не работает так же просто.