@cached property: примеры (PYTHON)
@cached_property(func: callable): cached_propertyОписание декоратора @cached_property
Декоратор @cached_property, доступный в модуле functools начиная с Python 3.8, преобразует метод класса в свойство, значение которого вычисляется только один раз за время жизни экземпляра. После первого обращения результат кэшируется и повторно используется при последующих вызовах, что повышает производительность для ресурсоемких вычислений.
Этот декоратор применяется к методам, которые не принимают аргументов, кроме self. Он не имеет собственных параметров для настройки. Возвращаемым значением является экземпляр cached_property, который ведет себя как дескриптор. При первом обращении к атрибуту выполняется декорированный метод, а его результат сохраняется в одноименном поле экземпляра. Последующие чтения возвращают кэшированное значение напрямую, без вызова метода.
Основное использование - оптимизация вычислений, результат которых стабилен для конкретного объекта. Декоратор не предназначен для свойств, которые должны пересчитываться при изменении внутреннего состояния.
Базовые примеры применения
Пример с вычислением площади круга:
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print('Вычисление площади')
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area) # Первый вызов
print(c.area) # Второй вызовВычисление площади 78.53975 78.53975
Пример с использованием в условии:
class DataProcessor:
def __init__(self, data):
self.data = data
@cached_property
def is_valid(self):
print('Проверка валидности')
return len(self.data) > 0
proc = DataProcessor([1, 2, 3])
if proc.is_valid and proc.is_valid:
print('Данные корректны')Проверка валидности Данные корректны
Альтернативные подходы в Python
Обычное свойство @property: Вычисляется каждый раз при обращении. Подходит для динамических данных, но неэффективно для тяжелых операций.
Декоратор @functools.lru_cache: Кэширует результаты вызовов функции с учетом аргументов. Может использоваться для методов, но требует аккуратности при работе с изменяемыми объектами.
Ручное кэширование в __init__: Вычисление значения в конструкторе и сохранение в атрибут. Просто, но не всегда удобно, если вычисление зависит от сложной логики.
Библиотечные решения: Например, django.utils.functional.cached_property в Django, который работает схожим образом.
Распространенные ошибки
Использование с методами, принимающими аргументы: Декоратор работает только с методами, у которых есть единственный параметр self.
from functools import cached_property
class Example:
@cached_property
def method(self, x): # Ошибка
return x * 2
e = Example()
print(e.method(5))TypeError: method() missing 1 required positional argument: 'x'
Изменение зависимых данных без инвалидации кэша: Если атрибуты, используемые в вычислении, изменяются, кэшированное значение становится неактуальным.
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area) # 78.53975
c.radius = 10
print(c.area) # Остается старым значение78.53975 78.53975
Попытка присвоения значения: По умолчанию @cached_property не поддерживает сеттер, но его можно добавить.
История изменений
Декоратор @cached_property был добавлен в Python 3.8. В более ранних версиях использовался аналогичный декоратор в Django или реализовывался вручную. Начиная с Python 3.8, он является частью стандартной библиотеки, что обеспечивает переносимость. В версии Python 3.12 были оптимизированы внутренние механизмы работы декоратора, но публичный API остался неизменным.
Расширенные сценарии использования
Пример с инвалидацией кэша через удаление атрибута:
from functools import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print('Вычисление площади')
return 3.14159 * self.radius ** 2
c = Circle(5)
print(c.area)
del c.area # Сброс кэша
print(c.area)Вычисление площади 78.53975 Вычисление площади 78.53975
Использование с наследованием классов:
class Shape:
@cached_property
def description(self):
return "Базовая фигура"
class Square(Shape):
def __init__(self, side):
self.side = side
@cached_property
def area(self):
return self.side ** 2
s = Square(4)
print(s.description)
print(s.area)Базовая фигура 16
Кэширование результата работы с файлом или сетью:
import json
from functools import cached_property
class ConfigLoader:
def __init__(self, filepath):
self.filepath = filepath
@cached_property
def data(self):
print('Загрузка конфигурации')
with open(self.filepath, 'r') as f:
return json.load(f)
loader = ConfigLoader('config.json')
print(loader.data['app_name'])
print(loader.data['version'])Загрузка конфигурации MyApp 1.0
Реализации в других языках
JavaScript (геттер): Используется ключевое слово get, но без автоматического кэширования.
class Circle {
constructor(radius) {
this.radius = radius;
this._area = null;
}
get area() {
if (!this._area) {
console.log('Вычисление площади');
this._area = Math.PI * this.radius ** 2;
}
return this._area;
}
}
let c = new Circle(5);
console.log(c.area);
console.log(c.area);Вычисление площади 78.53981633974483 78.53981633974483
Java (Lombok): Аннотация @Getter(lazy=true) генерирует код с ленивой инициализацией.
import lombok.Getter;
public class Circle {
private final double radius;
@Getter(lazy=true)
private final double area = calculateArea();
private double calculateArea() {
System.out.println("Вычисление площади");
return Math.PI * radius * radius;
}
}C# (свойство с ленивой загрузкой): Используется Lazy для отложенной инициализации.
using System;
public class Circle
{
private readonly double radius;
private readonly Lazy area;
public Circle(double radius)
{
this.radius = radius;
area = new Lazy(() => {
Console.WriteLine("Вычисление площади");
return Math.PI * radius * radius;
});
}
public double Area => area.Value;
}