В прошлой статье мы говорили о полезных функциях functools. Сегодня рассмотрим функции кэширования lru_cache
, cache
, cached_property
. Они вам могут пригодиться в тех случаях, когда ваши функции постоянно вычисляет одни и те же значения. Поэтому чтобы избежать повторных вычислений, вы можете добавить результаты вызова функций в кэше.
Функция lru_cache из functools
Функция lru_cache
предназначается для мемоизации, т.е. кэширует результат в памяти. Она используется в качестве декоратора функции, вызовы которой нужно сохранить в памяти вплоть до значения параметра maxsize
(по умолчанию 128).
Декоратор lru_cache
подходит для рекурсивных или постоянно вычисляющих функций. Так, вызывая такую функцию с одним и тем же аргументом, декоратор просто возьмет нужное значение из кэша, полученное в результате прошлых вызовов.
Продемонстрируем это на примере. Допустим мы написали рекурсивную функцию подсчета арифметической прогрессии. Вот так она выглядит:
def mysum(n): if n == 1: return n print(f"'{n}'", end=" ") return n + mysum(n - 1)
Мы специально добавили функцию print
для вывода в стандартный поток вывода текущего значения n
. Повызываем эту функцию с разными значениями:
>>> mysum(11) '11' '10' '9' '8' '7' '6' '5' '4' '3' '2' 66 >>> mysum(11) '11' '10' '9' '8' '7' '6' '5' '4' '3' '2' 66 >>> mysum(7) '7' '6' '5' '4' '3' '2' 28 >>> mysum(9) '9' '8' '7' '6' '5' '4' '3' '2' 45
Как видим, она постоянно вычисляет одни и те же значения n
. Итак, чтобы избежать повторных вычислений мы можем сохранить в кэше результаты предыдущих вычислений с помощью lru_cache
, добавив только одну строчку с декоратором:
import functools as ftl @ftl.lru_cache def mysum(n): if n == 1: return n print(f"'{n}'", end=" ") return n + mysum(n - 1) >>> mysum(11) '11' '10' '9' '8' '7' '6' '5' '4' '3' '2' 66 >>> mysum(11) 66 >>> mysum(9) 45 >>> mysum(7) 28 >>> mysum(15) '15' '14' '13' '12' 120
Поскольку результаты от sum(1)
до sum(11)
уже найдены и сохранены в кэше, то они из него достаются, поэтому mysum(15)
вычисляет вызовы от 15 до 12.
Как работает Least Recently Used (LRU) алгоритм. Параметры функции lru_cache
Алгоритм Least Recently Used (LRU) хранит maxsize
самых последних используемых вызовов. Иными словами, вызовы, которые давно не используются выбрасываются из кэша, если он переполнен. Один из способов реализовать алгоритм LRU — это использовать двусвязный список из результатов (это параметры и вызовы функции) и отображений ключей на указатели списка. При добавлении ключа указатель на первый элемент списка меняется на указатель данного ключа. При этом элемент в списке с этим ключом удаляется, ведь он все равно станет первым. Хотя в Python алгоритм LRU устроен немного сложнее, например, он также учитывает свое использование в тредах [2].
Итак, до этого мы использовали lru_cache
без явного задания параметров. Но на самом деле по умолчанию неявно передается maxsize=128
. Давайте зададим явно этот параметр, равным 3:
@ftl.lru_cache(maxsize=3) def mysum(n): if n == 1: return n print(f"'{n}'", end=" ") return n + mysum(n - 1) >>> mysum(10) '10' '9' '8' '7' '6' '5' '4' '3' '2' 55 >>> mysum(8) 36 >>> mysum(7) '7' '6' '5' '4' '3' '2' 28
Наша функция, теперь хранит только 3 последних вызова, т.е. значения 10, 9, 8 (не забывайте, что этой линейной нехвостовой рекурсии нужно возвратиться обратно, когда n = 1
). А вот значения 7 и меньше в кэше не хранятся, поэтому функция вычисляется как и положено. С другой стороны, если мы захотим теперь снова вычислить mysum(10)
, то вычислить нужно только mysum(8)
, mysum(9)
и mysum(10)
:
>>> mysum(10) '10' '9' '8' 55
Второй параметр декоратора lru_cache
является typed
, по умолчанию равный False
. Если он равен True
, то параметры декорируемой функции будут кэшированы отдельно. По умолчанию все параметры рассматриваются как эквивалентные. Это значит, что в некоторых случаях int
может быть эквивалентен float
(1 == 1.0
) или список эквивалентен кортежу. В нашей рекурсивной функции даже передача True
не гарантирует эквивалентности между int
и float
.
Также мы можем передать maxsize=None
, это сделает кэш бесконечным. В Python 3.9 появилась функция cache
, которая эквивалентна lru_cache(maxsize=None)
.
Изучаем информацию о кэше
Кэшированные функции имеют метод cache_info
, который выводит информацию о кэше:
>>> mysum.cache_info() CacheInfo(hits=2, misses=20, maxsize=3, currsize=3)
где
hits
— количество сохраненных вызовов, что были извлечены из кэша. Действительно, вызвав функцию с параметром 8, мы достали из кэша значениеmysum(8)
; с параметром 7, пришлось заново вычислять функцию; затем вызвав с параметром 10, мы достали из кэша значениеmysum(7)
misses
— количество раз когда, значение не было в кэше. Действительно,mysum(10)
вычисляла все 10 значений,mysum(8)
— просто достала значение из кэша,mysum(7)
— вычисляла 7 значений,mysum(10)
— вычисляла только значения 8, 9 и 10maxsize
— максимальное число значений, которые может хранить кэшcurrsize
— сколько значений занято в кэше на данный момент
Кэшируем геттеры класса с помощью cached_property
Еще одной невероятно полезной функцией является cached_property
, которая предназначена для кэширования атрибутов класса. Если ваш геттер постоянно что-то вычисляет, то разумно было сохранять результаты. Например, в коде на Python ниже метод sumdata
вызывает встроенную функцию sum
. Если экземпляр постоянно обращается к этому методу, то после декорирования cached_property
будет возвращаться значение из кэша.
class A: def __init__(self, data): self.data = data @ftl.cached_property def sumdata(self): return sum(self.data) >>> a = A([1, 2, 3, 4]) >>> a.sumdata 10 >>> a.sumdata # Заново не вычисляется 10
NLP с Python
Код курса
PNLP
Ближайшая дата курса
24 февраля, 2025
Продолжительность
40 ак.часов
Стоимость обучения
90 000 руб.
Еще больше подробностей о кэше в Python вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и IT-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:
- DPREP: Подготовка данных для Data Mining на Python
- Нейронные сети на Python
- NLP с Python
- Computer vision на Python