Using arguments in a decorator

Jon Clements joncle at googlemail.com
Fri Apr 20 12:10:15 EDT 2012


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?
> 
> 
> -- 
> Hate music? Then you'll hate this:
> 
> http://tinyurl.com/psymix

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

Jon





More information about the Python-list mailing list