Что такое @property
@property — это встроенный дескриптор, который позволяет объявить метод, но использовать его как атрибут без скобок:
class User:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
Теперь u.full_name вызывает геттер, но синтаксически выглядит как обычное поле. За этим стоит дескриптор: у объекта property определены __get__, __set__, __delete__ (для сеттеров/делитеров).
Преимущества:
инкапсуляция: можно начать с обычного поля, потом заменить его на вычисляемое свойство без изменения внешнего API;
валидация и побочные эффекты при чтении/записи/удалении;
совместимость с дескрипторной моделью Python.
Кэшируемое (ленивое) свойство
Задача: вычислить значение один раз при первом доступе и дальше возвращать кэш, не пересчитывая. Классический паттерн — вычислить и сохранить результат в __dict__ экземпляра:
class cached_property:
def __init__(self, func):
self.func = func
self.__doc__ = getattr(func, "__doc__")
self.name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
if self.name in instance.__dict__:
return instance.__dict__[self.name]
value = self.func(instance)
instance.__dict__[self.name] = value
return value
class Report:
@cached_property
def heavy_stats(self):
print("computing...")
# дорогой расчёт
return {"value": 42}
Вызовы r.heavy_stats впервые приведут к расчёту и записи в __dict__, последующие — просто вернут кэш.
Начиная с Python 3.8 есть готовый functools.cached_property, реализующий тот же паттерн. Senior‑разработчик должен понимать идею: дескриптор, который после первого вычисления «замораживает» результат в экземпляре.
Нюансы и расширения
если объект изменяем и кэш нужно сбрасывать при изменении других полей, нужно явно инвалидацию (удалять ключ из __dict__ или переинициализировать экземпляр);
в многопоточной среде возможны гонки при одновременном первом доступе — для критичных кейсов нужен lock вокруг вычисления;
можно комбинировать с @property.setter, если нужно вручную обновлять кэш.
Формулировка для собеседования
@property — это дескриптор, который позволяет объявить геттер/сеттер/делитер и работать с ними как с обычным атрибутом. Кэшируемое свойство реализуется либо через собственный дескриптор, который при первом доступе вычисляет значение и записывает его в instance.__dict__, либо через готовый functools.cached_property. На уровне senior ожидают понимание того, что за этим стоит дескрипторный протокол и как организовать инвалидацию и потокобезопасность при необходимости.