Как использовать `@property` с декораторами для вычисляемых атрибутов?

@property используется для создания вычисляемых атрибутов класса. Вместо хранения значения, атрибут вычисляется "на лету" при обращении к нему. Можно комбинировать с другими декораторами, например, @lru_cache для мемоизации результатов вычислений.
Пример:

from functools import lru_cache

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    @lru_cache(maxsize=None)
    def area(self):
        print("Calculating area...")
        return 3.14159 * self._radius ** 2
  
В этом примере, area - вычисляемый атрибут, который мемоизирован. Setter для radius используется для контроля изменения радиуса.

Использование @property с декораторами позволяет создавать вычисляемые атрибуты, которые выглядят и используются как обычные атрибуты, но их значения генерируются "на лету" при обращении к ним. Это мощный механизм для инкапсуляции логики вычислений и поддержания консистентности данных.

Основная идея состоит в том, что @property определяет метод, который будет вызываться при попытке получить значение атрибута. Дополнительно, можно использовать @.setter и @.deleter для определения методов, которые будут вызываться при попытке установить или удалить значение атрибута соответственно. В случае вычисляемых атрибутов, setter и deleter обычно не используются, потому что атрибут вычисляется и не должен устанавливаться напрямую.

Пример использования @property с декораторами для вычисляемого атрибута:


class Circle:
    def __init__(self, radius):
        self._radius = radius  # Используем _radius, чтобы избежать конфликта имен

    @property
    def radius(self):
        """Возвращает радиус круга."""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Устанавливает радиус круга. Проверяет, что радиус положительный."""
        if value <= 0:
            raise ValueError("Радиус должен быть положительным")
        self._radius = value

    @property
    def area(self):
        """Вычисляет и возвращает площадь круга."""
        return 3.14159 * self._radius * self._radius

    @property
    def diameter(self):
        """Вычисляет и возвращает диаметр круга."""
        return 2 * self._radius

В этом примере:

  • radius - обычный атрибут с getter и setter.
  • area - вычисляемый атрибут, который возвращает площадь круга, основанную на текущем радиусе.
  • diameter - вычисляемый атрибут, возвращающий диаметр.

Ключевые моменты:

  • @property преобразует метод area в атрибут, к которому можно обращаться как к circle.area, без необходимости вызова метода как circle.area().
  • Значение area вычисляется только при обращении к нему. Если радиус изменится, новое значение площади будет вычислено при следующем обращении к circle.area.
  • Использование @radius.setter позволяет контролировать процесс присвоения нового значения радиусу, добавляя валидацию. Это важно для поддержания целостности данных.
  • Для вычисляемых атрибутов, обычно, setter не определяют, если не требуется какой-то хитрый механизм обновления связанных данных.

Преимущества использования @property для вычисляемых атрибутов:

  • Чистый код: Атрибуты доступны как обычные атрибуты, что делает код более читаемым.
  • Инкапсуляция: Логика вычислений спрятана внутри класса.
  • Автоматическое обновление: Значение вычисляется только при обращении к атрибуту, гарантируя актуальность данных.
  • Контроль доступа: Возможность добавления валидации при установке значения атрибута (через setter).
  • Совместимость: Можно изменить реализацию атрибута (например, сделать его вычисляемым), не меняя интерфейс класса для пользователей.

В заключение, @property с декораторами – это мощный и элегантный способ реализации вычисляемых атрибутов в Python, позволяющий создавать более чистый, поддерживаемый и расширяемый код.

0