Using arguments in a decorator

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Apr 21 04:36:43 EDT 2012


On Fri, 20 Apr 2012 16:57:06 +0100, Rotwang wrote:

> 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

Note that this decorator is underpowered and arguable buggy: it 
suppresses the decorated function's doc string. You should use 
functools.wraps to copy the name, doc string and anything else necessary.

Here is how I would write the above.


import functools

def memoise(func):
    """Decorator to memoise a function."""
    cache = {}
    @functools.wraps(func)
    def inner(*args, **kwargs):
        # Make sure keyword args are always looked up in a consistent
        # order by sorting. One minor subtlety: since kwargs cannot 
        # have duplicate keys, sorted() will never try to break ties 
        # by comparing values. So this will work even for unsortable
        # values like complex numbers.
        kw = tuple(sorted(kwargs.items()))
        try:
            # Cache hit?
            result = cache[(args, kw)]
        except KeyError:
            # Cache miss.
            result = func(*args, **kwargs)
            cache[(args, kw)] = result
        except TypeError:
            # Cache failure; at least one argument is uncacheable.
            result = func(*args, **kwargs)
        # Is the cache too big?
        if len(cache) > 1000:
            # Dump an arbitrary item. A LRU (least recently used) cache
            # would be better, but this will do.
            cache.popitem()
        return result
    # Expose the cache to the outside world.
    inner._cache = cache
    return inner

[...]
> 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?


http://docs.python.org/release/3.1.5/library/inspect.html?#inspect.getfullargspec


>>> def f(a, b=1, *args, c, d=2, **kwargs):
...     x = 42
...     return (a,b,c,d,args,kwargs)
...
>>> import inspect
>>> inspect.getfullargspec(f)
FullArgSpec(args=['a', 'b'], varargs='args', varkw='kwargs', 
defaults=(1,), kwonlyargs=['c', 'd'], kwonlydefaults={'d': 2}, 
annotations={})





-- 
Steven




More information about the Python-list mailing list