Using arguments in a decorator

Rotwang sg552 at hotmail.co.uk
Fri Apr 20 17:05:14 EDT 2012


On 20/04/2012 17:10, Jon Clements wrote:
> On Friday, 20 April 2012 16:57:06 UTC+1, Rotwang  wrote:
>> Hi all, here's a problem I don't know how to solve. I'm using Python 2.7.2.
>>
>> I'm doing some stuff in Python which means I have cause to call
>> functions that take a while to return. Since I often want to call such a
>> function more than once with the same arguments, I've written a
>> decorator to eliminate repeated calls by storing a dictionary whose
>> items are arguments and their results:
>>
>> def memo(func):
>>       def memofunc(*args, **kwargs):
>>           twargs = tuple(kwargs.items())
>>           if (args, twargs) in memofunc.d:
>>               return copy(memofunc.d[(args, twargs)])
>>           memofunc.d[(args, twargs)] = func(*args, **kwargs)
>>           return copy(memofunc.d[(args, twargs)])
>>       memofunc.__name__ = func.__name__
>>       memofunc.d = {}
>>       return memofunc
>>
>>
>> If a function f is decorated by memo, whenever f is called with
>> positional arguments args and keyword arguments kwargs, the decorated
>> function defines twargs as a hashable representation of kwargs, checks
>> whether the tuple (args, twargs) is in f's dictionary d, and if so
>> returns the previously calculated value; otherwise it calculates the
>> value and adds it to the dictionary (copy() is a function that returns
>> an object that compares equal to its argument, but whose identity is
>> different - this is useful if the return value is mutable).
>>
>> As far as I know, the decorated function will always return the same
>> value as the original function. The problem is that the dictionary key
>> stored depends on how the function was called, even if two calls should
>> be equivalent; hence the original function gets called more often than
>> necessary. For example, there's this:
>>
>>   >>>  @memo
>> def f(x, y = None, *a, **k):
>> 	return x, y, a, k
>>
>>   >>>  f(1, 2)
>> (1, 2, (), {})
>>   >>>  f.d
>> {((1, 2), ()): (1, 2, (), {})}
>>   >>>  f(y = 2, x = 1)
>> (1, 2, (), {})
>>   >>>  f.d
>> {((1, 2), ()): (1, 2, (), {}), ((), (('y', 2), ('x', 1))): (1, 2, (), {})}
>>
>>
>> What I'd like to be able to do is something like this:
>>
>> def memo(func):
>>       def memofunc(*args, **kwargs):
>>           #
>>           # define a tuple consisting of values for all named positional
>>           # arguments occurring in the definition of func, including
>>           # default arguments if values are not given by the call, call
>>           # it named
>>           #
>>           # define another tuple consisting of any positional arguments
>>           # that do not correspond to named arguments in the definition
>>           # of func, call it anon
>>           #
>>           # define a third tuple consisting of pairs of names and values
>>           # for those items in kwargs whose keys are not named in the
>>           # definition of func, call it key
>>           #
>>           if (named, anon, key) in memofunc.d:
>>               return copy(memofunc.d[(named, anon, key)])
>>           memofunc.d[(named, anon, key)] = func(*args, **kwargs)
>>           return copy(memofunc.d[(named, anon, key)])
>>       memofunc.__name__ = func.__name__
>>       memofunc.d = {}
>>       return memofunc
>>
>>
>> But I don't know how. I know that I can see the default arguments of the
>> original function using func.__defaults__, but without knowing the
>> number and names of func's positional arguments (which I don't know how
>> to find out) this doesn't help me. Any suggestions?
>
> Possibly take a look at functools.lru_cache (which is Python 3.2+), and use the code from that (at it's part of the stdlib, someone must have done design and testing on it!). http://hg.python.org/cpython/file/default/Lib/functools.py

Thanks for your reply, but this decorator doesn't seem to attempt to get 
around the problem I mentioned. This is pasted from IDLE:

 >>> from functools import lru_cache
 >>> @lru_cache(None)
def f(x, y = None, *a, **k):
	return x, y, a, k

 >>> f(1, 2)
(1, 2, (), {})
 >>> f.cache_info()
CacheInfo(hits=0, misses=1, maxsize=None, currsize=1)
 >>> f(y = 2, x = 1)
(1, 2, (), {})
 >>> f.cache_info()
CacheInfo(hits=0, misses=2, maxsize=None, currsize=2)


-- 
Hate music? Then you'll hate this:

http://tinyurl.com/psymix



More information about the Python-list mailing list