Functools.lru cache: примеры (PYTHON)
functools.lru_cache(maxsize: int, typed: bool): callableФункция functools.lru_cache
Декоратор @lru_cache из модуля functools реализует кэширование результатов выполнения функции по принципу LRU (Least Recently Used - «вытеснение давно неиспользуемых»). Его основное назначение - мемоизация, то есть сохранение результатов вызовов функции для конкретных наборов аргументов с целью избежать повторных вычислений.
Использование декоратора эффективно для функций, которые:
- Вызываются многократно с одинаковыми аргументами.
- Имеют «тяжелые» вычисления или ввод-вывод.
- Являются детерминированными (возвращают одинаковый результат для одних и тех же аргументов).
- Часто используются в рекурсивных алгоритмах.
Декоратор принимает два необязательных аргумента:
- maxsize: Максимальное количество записей, хранящихся в кэше. По умолчанию равно 128. Если установлено в
None, кэш может расти без ограничений, что может привести к утечке памяти. - typed: Булев флаг. Если установлен в
True, аргументы разных типов (например,5и5.0) будут кэшироваться отдельно. По умолчаниюFalse.
Декорированная функция получает дополнительные методы: cache_info() (возвращает статистику использования), cache_clear() (очищает кэш) и __wrapped__ (ссылка на исходную функцию).
Примеры использования
Пример базового применения для вычисления факториала:
from functools import lru_cache
@lru_cache(maxsize=32)
def factorial(n):
if n < 2:
return 1
return n * factorial(n-1)
print(factorial(10))
print(factorial.cache_info())3628800 CacheInfo(hits=8, misses=11, maxsize=32, currsize=11)
Пример с параметром typed:
@lru_cache(typed=True)
def process_value(x):
return x * 2
print(process_value(5))
print(process_value(5.0))
print(process_value.cache_info())10 10.0 CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
Пример без ограничения размера кэша:
@lru_cache(maxsize=None)
def get_heavy_data(key):
# Имитация тяжелой операции
return hash(str(key))
print(get_heavy_data('test'))
print(get_heavy_data.cache_info())-662733300 CacheInfo(hits=0, misses=1, maxsize=None, currsize=1)
Похожие средства в Python
functools.cache: Упрощенная версия lru_cache(maxsize=None), появившаяся в Python 3.9. Не имеет ограничения размера и не вытесняет записи. Подходит для сценариев, где количество уникальных вызовов предсказуемо и невелико.
functools.cached_property: Декоратор для методов класса, который преобразует метод в свойство, вычисляемое единожды за время жизни экземпляра. Результат кэшируется в атрибуте экземпляра. Используется для ленивых вычислений атрибутов объекта.
Сторонние библиотеки: Библиотеки, такие как cachetools, предлагают больше вариантов политик кэширования (TTL, LFU). Их выбор оправдан при необходимости более гибкого управления кэшем.
Аналоги в других языках программирования
JavaScript: Аналогичная функциональность часто реализуется вручную с использованием Map или объектов. Пример простой мемоизации:
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const slowFunc = (x) => x * 2;
const cachedFunc = memoize(slowFunc);
console.log(cachedFunc(5));
console.log(cachedFunc(5));10 10
Java: Используется аннотация @Cacheable в Spring Framework или библиотека Guava с CacheBuilder. Пример с Guava:
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(100)
.build(new CacheLoader() {
public Integer load(Integer key) {
return key * 2;
}
});
System.out.println(cache.get(5)); 10
Go: Стандартной библиотеки не содержит готового декоратора. Реализация требует создания структуры с map и мьютексом для потокобезопасности. Пример простого кэша:
type Cache struct {
sync.Mutex
m map[interface{}]interface{}
}
func (c *Cache) Get(key interface{}, compute func() interface{}) interface{} {
c.Lock()
defer c.Unlock()
if v, ok := c.m[key]; ok {
return v
}
v := compute()
c.m[key] = v
return v
}PHP: Функция apc_fetch/apc_store для кэширования в памяти или использование библиотек типа Symfony Cache. Пример:
function memoize($callable) {
static $cache = [];
return function() use ($callable, &$cache) {
$key = serialize(func_get_args());
if (!isset($cache[$key])) {
$cache[$key] = call_user_func_array($callable, func_get_args());
}
return $cache[$key];
};
}
$cachedFunc = memoize(fn($x) => $x * 2);
echo $cachedFunc(5);10
Типичные ошибки
Кэширование функций с нехэшируемыми аргументами: lru_cache требует, чтобы аргументы функции были хэшируемыми (например, списки - нет).
from functools import lru_cache
@lru_cache
def process_list(lst):
return sum(lst)
process_list([1, 2, 3])TypeError: unhashable type: 'list'
Изменяемые возвращаемые значения: Кэш возвращает ссылку на один и тот же объект. Если функция возвращает изменяемый объект (список, словарь) и вызывающий код меняет его, это повлияет на все последующие возвращаемые значения из кэша.
@lru_cache
def get_list():
return []
lst1 = get_list()
lst1.append(1)
lst2 = get_list()
print(lst2)[1]
Неуместное использование для функций с побочными эффектами: Кэширование подавляет повторные вызовы, что нежелательно для функций, выполняющих ввод-вывод или изменяющих глобальное состояние.
Изменения в последних версиях
В Python 3.8 был добавлен параметр typed.
В Python 3.9 была добавлена функция functools.cache как синтаксический сахар для lru_cache(maxsize=None). Также была улучшена производительность самого декоратора lru_cache.
Начиная с Python 3.10, в functools.lru_cache и functools.cache добавлен метод cache_parameters(), возвращающий словарь с параметрами кэша (maxsize и typed).
Расширенные примеры
Использование с пользовательскими классами, реализующими __hash__ и __eq__:
from functools import lru_cache
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
@lru_cache
def distance(p1, p2):
return ((p1.x - p2.x)**2 + (p1.y - p2.y)**2) ** 0.5
p_a = Point(0, 0)
p_b = Point(3, 4)
print(distance(p_a, p_b))
print(distance.cache_info())5.0 CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)
Кэширование методов класса с использованием @lru_cache на методе, но необходимо учитывать, что первый аргумент self также будет закэширован. Чаще для методов используют @cached_property.
class DataService:
def __init__(self):
self.call_count = 0
@lru_cache(maxsize=1)
def get_config(self, env):
self.call_count += 1 # Эта строка выполнится только при промахе кэша
return {'env': env, 'data': 'heavy_computation'}
service = DataService()
print(service.get_config('prod'))
print(service.get_config('prod'))
print(f"Call count: {service.call_count}"){'env': 'prod', 'data': 'heavy_computation'}
{'env': 'prod', 'data': 'heavy_computation'}
Call count: 1Сброс кэша и использование __wrapped__ для доступа к оригинальной функции:
@lru_cache
def square(x):
return x * x
print(square(5))
square.cache_clear()
print(square.cache_info())
print(square.__wrapped__(5)) # Вызов без кэширования25 CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 25
Использование с рекурсивной функцией, где выгода от кэширования максимальна:
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(50))
print(fibonacci.cache_info())12586269025 CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)