2016-04-21 2 views
0

Я хотел бы сделать декоратор python, который запоминает функции. Например, еслиMemoization/Caching с необязательными аргументами по умолчанию

@memoization_decorator  
def add(a, b, negative=False): 
    print "Computing" 
    return (a + b) * (1 if negative is False else -1) 

add(1, 2) 
add(1, b=2) 
add(1, 2, negative=False) 
add(1, b=2, negative=False) 
add(a=1, b=2, negative=False) 
add(a=1, b=2) 

Я хотел бы выход будет

Computing 
3 
3 
3 
3 
3 
3 

и выход должен быть одинаковым при любой перестановке последних 6 строк.

Это сводится к нахождению карты, отправляющей эквивалентные наборы *args, **kwargs** к уникальному ключу для кеша memoization dict. В приведенном выше примере имеет *args, **kwargs равен

(1, 2), {} 
(1,), {'b': 2} 
(1, 2), {'negative': False} 
(1,), {'b': 2, 'negative': False} 
(), {'a': 1, 'b': 2, 'negative': False} 
(), {'a': 1, 'b': 2} 

ответ

4

Для запоминания вы можете использовать functools.lru_cache().

Редактировать: Проблема с этим для вашего случая использования заключается в том, что он не считает, что два вызова функций одинаковы, если они определяют их аргументы. Для решения этой проблемы мы можем написать свой собственный декоратор, который сидит на вершине lru_cache() и преобразует аргументы в одну каноническую форму:

from functools import lru_cache, wraps 
import inspect 

def canonicalize_args(f): 
    """Wrapper for functools.lru_cache() to canonicalize default               
    and keyword arguments so cache hits are maximized.""" 

    @wraps(f) 
    def wrapper(*args, **kwargs): 
     sig = inspect.getargspec(f.__wrapped__) 

     # build newargs by filling in defaults, args, kwargs                
     newargs = [None] * len(sig.args) 
     newargs[-len(sig.defaults):] = sig.defaults 
     newargs[:len(args)] = args 
     for name, value in kwargs.items(): 
      newargs[sig.args.index(name)] = value 

     return f(*newargs) 

    return wrapper 

@canonicalize_args 
@lru_cache() 
def add(a, b, negative=False): 
    print("Computing") 
    return (a + b) * (1 if negative is False else -1) 

Теперь add() вызывается только один раз для всего набора вызовов в вопросе. Каждый вызов выполняется со всеми тремя аргументами, указанными позиционно.

+0

Я пробовал то, что вы предлагали, и он не работает. Например, выполнение '' add (1,2) '' и '' add (1, 2, negative = False) '' последовательно дает '' Computing \\ 3'' оба раза, поэтому вычисляется второй вызов а не возвращаться из кеша. –

+0

@JonWarneke: Я понимаю, что вы имеете в виду. Я расширил свой ответ, включив в него полное решение. –

+0

Это, кажется, сломано для функции, которая принимает '** kwargs' в дополнение к регулярным аргументам. – ThiefMaster

1

Вы можете использовать inspect.getcallargs(), чтобы получить список канонических аргументов для функции. Обернуть его в декоратор не должно быть слишком сложно.

In [1]: def add(a, b, negative=False): 
    ...:  print("Computing") 
    ...:  return (a + b) * (1 if negative is False else -1) 
    ...: 
    ...: 

In [2]: inspect.getcallargs(add, 1, 2) 
Out[2]: {'a': 1, 'b': 2, 'negative': False} 

In [3]: inspect.getcallargs(add, 1, 2, True) 
Out[3]: {'a': 1, 'b': 2, 'negative': True} 

In [4]: inspect.getcallargs(add, 1, 2, negative=False) 
Out[4]: {'a': 1, 'b': 2, 'negative': False} 

In [5]: inspect.getcallargs(add, 1, b=2, negative=False) 
Out[5]: {'a': 1, 'b': 2, 'negative': False} 

In [6]: inspect.getcallargs(add, 1, b=2) 
Out[6]: {'a': 1, 'b': 2, 'negative': False} 

 Смежные вопросы

  • Нет связанных вопросов^_^