Что такое @property? Как реализовать кэшируемое свойство?

@property в Python — это дескриптор, который превращает метод в управляемое атрибут‑свойство: при чтении вызывается геттер вместо прямого доступа к полю; кэшируемое свойство обычно реализуют через собственный дескриптор или через ленивое вычисление и сохранение результата в __dict__ (например, functools.cached_property).
Подробный ответ

Что такое @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 ожидают понимание того, что за этим стоит дескрипторный протокол и как организовать инвалидацию и потокобезопасность при необходимости.

Оцени свой прогресс

Честно оцени своё понимание этого вопроса, чтобы мы могли построить твой учебный трек максимально эффективно.
Читать в блоге