Напишите декоратор cached который будет кэшировать результат вызова функции
Создайте декоратор makeCaching(f) , который берет функцию f и возвращает обёртку, которая кеширует её результаты.
В этой задаче функция f имеет только один аргумент, и он является числом.
- При первом вызове обёртки с определённым значением аргумента – она вызывает f и запоминает её результат.
- При втором и последующих вызовах с тем же значением аргумента – возвращается сохранённое значение результата.
Должно работать так:
function f(x) < return Math.random() * x; // random для удобства тестирования >function makeCaching(f) < /* ваш код */ >f = makeCaching(f); var a, b; a = f(1); b = f(1); alert( a == b ); // true (значение закешировано) b = f(2); alert( a == b ); // false, другой аргумент => другое значение
Запоминать результаты вызова функции будем в замыкании, в объекте cache: < ключ:значение >.
function f(x) < return Math.random()*x; >function makeCaching(f) < var cache = <>; return function(x) < if (!(x in cache)) < cache[x] = f.call(this, x); >return cache[x]; >; > f = makeCaching(f); var a = f(1); var b = f(1); alert( a == b ); // true (значение закешировано) b = f(2); alert( a == b ); // false, другой аргумент => другое значение
Обратите внимание: проверка на наличие уже подсчитанного значения выглядит так: if (x in cache) . Менее универсально можно проверить так: if (cache[x]) , это если мы точно знаем, что cache[x] никогда не будет false , 0 и т.п.
function makeCaching(f) < var cache = <>; return function(x) < if (!(x in cache)) < cache[x] = f.call(this, x); >return cache[x]; >; >
Кэширование возвращаемых значений в декораторах
Декораторы могут обеспечить хороший механизм для кэширования и запоминания. В качестве примера рассмотрим рекурсивное определение последовательности Фибоначчи:
def fib(num): if num 2: return num return fib(num - 1) + fib(num - 2)
Хотя реализация проста, ее производительность во время выполнения ужасна:
>>> start = time.perf_counter(); fib(20); print('Time run:', time.perf_counter() - start) # 6765 # Time run: 0.005267535000712087 >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.1682777839996561
Чтобы вычислить десятое число Фибоначчи, вам нужно вычислить только предыдущие числа Фибоначчи, но эта реализация каким-то образом требует колоссальных 177 вычислений. Ситуация быстро ухудшается для fib(20) — 21891 вычисление и почти 2,7 миллиона вычислений для fib(30) . Это происходит потому, что код продолжает вычислять числа Фибоначчи, которые уже известны.
Обычным решением является реализация чисел Фибоначчи с использованием цикла for . in и таблицы подстановки. Простое кэширование вычислений также делает свое дело:
import functools def cache(func): """Кэш предыдущих вызовов функций""" @functools.wraps(func) def wrapper(*args, **kwargs): cache_key = args + tuple(kwargs.items()) if cache_key not in wrapper.cache: wrapper.cache[cache_key] = func(*args, **kwargs) return wrapper.cache[cache_key] wrapper.cache = dict() return wrapper @cache def fib(num): if num 2: return num return fibonacci(num - 1) + fibonacci(num - 2)
Кэш работает как таблица подстановки, поэтому теперь функция fib() выполняет необходимые вычисления только один раз. Это сразу заметно по времени выполнения функции. Сравните с предыдущим запуском функции, без кэширующего декоратора в начале материала:
>>> import time >>> start = time.perf_counter(); fib(20); print('Time run:', time.perf_counter() - start) # 6765 # Time run: 4.560499928629724e-05 >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.000410601000112365
В стандартной библиотеке доступны 2 кэширующих декоратора:
- Декоратор @functools.cache модуля functools представляет собой простой легкий неограниченный кеш функций. Иногда называется «memoization».
- Декоратор @functools.lru_cache кэш LRU. Этот декоратор имеет больше возможностей, чем тот, который представлен для примера.
import functools, time @functools.lru_cache(maxsize=50) def fib(num): if num 2: return num return fib(num - 1) + fib(num - 2) >>> start = time.perf_counter(); fib(30); print('Time run:', time.perf_counter() - start) # 832040 # Time run: 0.00011347100007697009 >>> fib.cache_info() # CacheInfo(hits=28, misses=31, maxsize=50, currsize=31)
Можно использовать метод fib.cache_info() , чтобы увидеть, как работает кэш, так-же можно его настроить, если это необходимо.
В декораторе @functools.lru_cache(maxsize=50) , параметр maxsize указывает сколько последних вызовов кэшируется. Значение по умолчанию равно 128, но вы можете указать maxsize=None для кэширования всех вызовов функций. Однако имейте в виду, что это может вызвать проблемы с памятью.
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Захват аргументов функцией декоратором
- Возврат значений из декорируемой функции
- Атрибут __name__ декорируемой функции
- Шаблон декоратора общего назначения
- Декораторы с аргументами
- Вложенные декораторы
- Кэширование значений в декораторах Python
- Использование класса как декоратора
- Декорирование методов класса
- Декорирование классов
Задание из Яндекс Практикума по Python, Декораторы
«Задание: Напишите функцию-декоратор, которая сохранит (закеширует) значение декорируемой функции. Если декорируемая функция будет вызвана повторно с теми же параметрами — декоратор должен вернуть сохранённый результат, не выполняя функцию. Подсказка: Создайте словарь и при каждом вызове декоратора сохраняйте в нём аргументы задекорированной функции. При каждом вызове проверяйте, не было ли уже аналогичного вызова. Если был — верните результат прошлого вызова, если не было — верните результат декорируемой функции и одновременно сохраните этот результат в словарь. Ключом для каждой записи словаря может быть аргумент декорируемой функции.» Собственно, код. Написать требуется функцию cache_args, но как ни бьюсь — она работает неверно.
import time from functools import wraps def time_check(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) execution_time = round(time.time() - start_time, 1) print(f'Время выполнения функции : с.') return result return wrapper def cache_args(func): @wraps(func) def wrapper1(*args, **kwargs): result = func(*args, **kwargs) x = [] for t in x: if t == result: return t x = x.append(result) return result return wrapper1 @time_check @cache_args def long_heavy(num): time.sleep(1) return num * 2 print(long_heavy(1)) # Время выполнения функции long_heavy: 1.0 с. # 2 print(long_heavy(1)) # Время выполнения функции long_heavy: 0.0 с. # 2 print(long_heavy(2)) # Время выполнения функции long_heavy: 1.0 с. # 4 print(long_heavy(2)) # Время выполнения функции long_heavy: 0.0 с. # 4 print(long_heavy(2)) # Время выполнения функции long_heavy: 0.0 с. # 4
Отслеживать
2,313 2 2 золотых знака 10 10 серебряных знаков 38 38 бронзовых знаков
задан 27 окт 2021 в 18:03
Crystallice Crystallice
1 1 1 серебряный знак 2 2 бронзовых знака
В подсказке написано создать словарь и хранить там аргументы. А у вас этого нет. У вас везде один аргумент. Вам действительно нужно обрабатывать все арги и кварги? И помнить надо все предыдущие запуски функции, или только предыдущий?
27 окт 2021 в 18:42
@Alpensin x = x.append(result) не дополняет словарь аргументами, получается? Насчет аргов и кваргов не знаю, сделал по аналогии с уже заданной time_check. Судя по заданию, помнить надо все результаты, но если исходить из проверок — то хватит и только предыдущего (и это закодить будет гораздо проще, как мне кажется).
27 окт 2021 в 19:05
@Crystallice Не надо упрощать, там и так проще некуда всё. Вы хотя бы разницу между словарём и списком понимаете? [] — это list , т.е. список. И инициализировать кэш надо вне wrapper1 , иначе при каждом вызове wrapper1 он у вас будет сбрасываться. И func надо вызывать не всегда, а только если результат её вызова с таким же аргументом не закэширован.
27 окт 2021 в 19:08
То есть, я правильно понимаю, что при вызове cache_args будет выполняться только wrapper1, и если словарь определить в cashe_args, то он перезаписываться каждый раз не будет?
27 окт 2021 в 19:39
@Alpensin Ну, почти так. Только словарь в принципе такой
Как работать с кэшированием данных в Python
Узнайте, как оптимизировать работу Python-приложений с помощью кэширования данных, используя встроенный декоратор и внешние хранилища.
Алексей Кодов
Автор статьи
23 июня 2023 в 18:57
Кэширование данных — это важный аспект в разработке приложений, так как это позволяет оптимизировать процесс загрузки и обработки информации. В этой статье мы разберем основные принципы кэширования данных в Python и покажем примеры его использования.
Что такое кэширование данных?
Кэширование данных — это процесс хранения результатов выполнения функций и запросов во временном хранилище (кэше) с целью ускорения повторного обращения к ним. Вместо того чтобы заново выполнять те же операции, можно просто получить результат из кэша, что существенно экономит время и ресурсы.
Встроенный декоратор functools.lru_cache
Python предоставляет встроенный декоратор functools.lru_cache , который позволяет легко кэшировать результаты функций. LRU (Least Recently Used) кэш — это кэш с ограниченным размером, который автоматически удаляет наименее недавно использованные элементы при необходимости освободить место для новых данных.
Пример использования functools.lru_cache :
import functools @functools.lru_cache(maxsize=100) def expensive_function(arg1, arg2): # Здесь выполняется какая-то дорогостоящая операция result = arg1 + arg2 return result # Вызов функции result = expensive_function(1, 2)
В этом примере результаты expensive_function будут кэшироваться и сохраняться в LRU-кэше размером до 100 элементов.
Python-разработчик: новая работа через 9 месяцев
Получится, даже если у вас нет опыта в IT

Кэширование с использованием внешних хранилищ
В некоторых случаях может потребоваться кэширование данных с использованием внешних хранилищ, таких как Redis или Memcached. Это особенно актуально при работе с веб-приложениями и распределенными системами.
Пример кэширования данных с использованием Redis:
import redis cache = redis.Redis(host='localhost', port=6379) def cache_data(key, data, ttl=3600): cache.setex(key, ttl, data) def get_cached_data(key): return cache.get(key) # Кэширование данных cache_data('my_key', 'my_data') # Получение данных из кэша result = get_cached_data('my_key')
В этом примере мы используем библиотеку redis для работы с кэшем Redis. Функции cache_data и get_cached_data позволяют сохранять и получать данные из кэша соответственно.
Заключение
Кэширование данных является полезным инструментом для оптимизации работы приложений и снижения нагрузки на серверы. В Python существует несколько способов реализации кэширования, включая встроенный декоратор functools.lru_cache и использование внешних хранилищ, таких как Redis или Memcached. Экспериментируйте с различными подходами и выбирайте тот, который наиболее подходит для вашего проекта.
