Изменяемый объект в локальной области видимости как кэш для декоратора
Есть правило - не использовать изменяемые объекты в сигнатуре функции, так как они создаются единожды при создании объекта - функция. Иногда это можно использовать осознанно, если необходим один и тот же объект для каждого вызова функции (не берём в расчёт, что может быть выполнен вызов с указанием объекта и кэш не будет использоваться). Например, для "кэширования" результатов работы другой функции.
Пример с декоратором, который выполняет работу с кэшем для "долгоиграющих функция".
import time
args = (1,2,1,2) # создаю набор аргуметов для функции "f"
def caching(fn,cache = dict()):
"""декоратор для создания кэша результатов функции
cash = dict() специально вынесена в сигнатуру, так как объект
на который указывает имя cash будет создан единожды при создании объекта - функция
"""
def wrapper(i):
result = cache.get(i) # ищу в кэше
if not result:
result = cache.setdefault(i,fn(i)) # в кэше нет, вычисляю и помещаю в кэш
else:
print('(результат возвращён из кэша) ',end='')
return result
return wrapper
@caching
def f(arg):
time.sleep(3) # имитирую долгое вычисление
return arg
if __name__ =='__main__':
for i in args:
print(f'result: {f(i)}')
Такого же эффекта можно добиться, переместив создание пустого словаря и имени для него уже в тело декоратора
import time
args = (1,2,1,2) # создаю набор аргуметов для функции "f"
def caching(fn,):
"""декоратор для создания кэша результатов функции
"""
cache = dict()
print(id(cache))
def wrapper(i):
result = cache.get(i) # ищу в кэше
if not result:
result = cache.setdefault(i,fn(i)) # в кэше нет, вычисляю и помещаю в кэш
else:
print('(результат возвращён из кэша) ',end='')
return result
return wrapper
@caching
def f(arg):
time.sleep(3) # имитирую долгое вычисление
return arg
if __name__ =='__main__':
for i in args:
print(f'result: {f(i)}')
Тут уже с натяжкой, но понятно, что при создании функции содаётся локальное пространство имён и добавляя туда изменяемый объект и он фигурирует в этом пространстве имён.
Но, непонятно другое, почему вызов print(id(cash)) так же выполняется только один раз при создании объекта функция?
Ответы (1 шт):
print(id(cache)) выполняется столько раз, сколько вызывается функция-декоратор caching. Она вызывается один раз в строке @caching.
Если вы будете кэшировать несколько разных функций, она вызовется несколько раз.
Важно понимать что происходит при декорировании. Код
@caching
def f(arg):
time.sleep(3)
return arg
переводится компилятором в
def f(arg):
time.sleep(3)
return arg
f = caching(f)
Последнее присваивание приводит к тому, что теперь под именем f прячется wrapper из декоратора и именно его теперь будут вызывать под этим именем. Проверить не сложно:
print(f)
напечатает что-то такое:
<function caching.<locals>.wrapper at 0x7f3fb7415760>