Я хочу создать декоратор «кеш» для методов класса, который регистрирует во внутреннем свойстве класса результат метода, позволяющего избежать его вычисления несколько раз (и я не хочу использовать простое свойство, вычисленное в __init__
, потому что я не уверен, что его вычислить даже раз все время).Python decorator обрабатывает аргументы по умолчанию для декорированной функции
Первая идея заключается в том, чтобы создать декоратор «кэш», похожее на это:
def cache(func):
name = "_{:s}".format(func.__name__)
def wrapped(obj):
if not hasattr(obj, name) or getattr(obj, name) is None:
print "Computing..."
setattr(obj, name, func(obj))
else:
print "Already computed!"
return getattr(obj, name)
return wrapped
class Test:
@cache
def hello(self):
return 1000 ** 5
Все работает отлично:
In [121]: t = Test()
In [122]: hasattr(t, '_hello')
Out[122]: False
In [123]: t.hello()
Computing...
Out[123]: 1000000000000000
In [124]: t.hello()
Already computed!
Out[124]: 1000000000000000
In [125]: hasattr(t, '_hello')
Out[125]: True
Теперь давайте скажем, что я хочу сделать то же самое, но когда метод можно вызвать с аргументами (ключевое слово и/или нет). Конечно, теперь мы будем хранить результаты не в отдельных свойствах (какими были бы имена? ...), а в словаре, ключи которого состоят из * args и ** kwargs. Давайте делать это с кортежами:
def cache(func):
name = "_{:s}".format(func.__name__)
def wrapped(obj, *args, **kwargs):
if not hasattr(obj, name) or getattr(obj, name) is None:
setattr(obj, name, {})
o = getattr(obj, name)
a = args + tuple(kwargs.items())
if not a in o:
print "Computing..."
o[a] = func(obj, *args, **kwargs)
else:
print "Already computed!"
return o[a]
return wrapped
class Test:
@cache
def hello(self, *args, **kwargs):
return 1000 * sum(args) * sum(kwargs.values())
In [137]: t = Test()
In [138]: hasattr(t, '_hello')
Out[138]: False
In [139]: t.hello()
Computing...
Out[139]: 0
In [140]: hasattr(t, '_hello')
Out[140]: True
In [141]: t.hello(3)
Computing...
Out[141]: 0
In [142]: t.hello(p=3)
Computing...
Out[142]: 0
In [143]: t.hello(4, y=23)
Computing...
Out[143]: 92000
In [144]: t._hello
Out[144]: {(): 0, (3,): 0, (4, ('y', 23)): 92000, (('p', 3),): 0}
Благодаря тому, что метод items
превращает словарь в кортеже, не принимая во внимание на порядок в словаре, он отлично работает, если keyworded аргументы не называются в те же заказы:
In [146]: t.hello(2, a=23,b=34)
Computing...
Out[146]: 114000
In [147]: t.hello(2, b=34, a=23)
Already computed!
Out[147]: 114000
Вот моя проблема: если метод имеет аргументы по умолчанию, то он больше не работает:
class Test:
@cache
def hello(self, a=5):
return 1000 * a
Теперь не приста K больше:
In [155]: t = Test()
In [156]: t.hello()
Computing...
Out[156]: 5000
In [157]: t.hello(a=5)
Computing...
Out[157]: 5000
In [158]: t.hello(5)
Computing...
Out[158]: 5000
In [159]: t._hello
Out[159]: {(): 5000, (5,): 5000, (('a', 5),): 5000}
Результат вычисляется в 3 раза, потому что аргументы не дают один и тот же путь (даже если они являются «же» аргумент!).
Кто-нибудь знает, как я мог поймать значения «по умолчанию», заданные функции, внутри декоратора?
Спасибо
Знаете ли вы [functools.lru_cache] (https://docs.python.org/3/library/functools.html#functools.lru_cache)? –
@Jonas Wielicki Это не доступно на Python ниже 3.2. Он использует Python 2.x –
@VadimShkaberda вправо, я не заметил, что он был добавлен в 3.2. –