recursive decorator

Ethan Furman ethan at stoneleaf.us
Wed Sep 2 18:19:13 EDT 2009


Greetings, List!

The recent thread about a recursive function in a class definition led 
me back to a post about bindfunc from Arnaud, and from there I found 
Michele Simionato's decorator module (many thanks! :-), and from there I 
began to wonder...

from decorator import decorator

@decorator
def recursive1(func, *args, **kwargs):
     return func(func, *args, **kwargs)

@recursive1
def factorial1(recurse, n):
     if n < 2:
         return 1
     return n * recurse(n-1)

factorial(4)
TypeError: factorial1() takes exactly 2 arguments (1 given)

Now it's *entirely* possible that I am missing something easy here, but 
at any rate I was thinking about the above error, about how one of the 
design goals behind the decorator module was to enable introspection to 
give accurate information about usage, etc, and how if the signature was 
*exactly* preserved then we have this pesky and seemingly out of place 
variable, not mention we can't 'just use it' in the code.

So I set out to create a signature-modifying decorator that would lose 
that first parameter from the wrapper, while keeping the rest of the 
signature intact.  This is what I was able to come up with:

def recursive(func):
     """
     recursive is a signature modifying decorator.
     Specifially, it removes the first argument, so
     that calls to the decorated function act as
     if it does not exist.
     """
     argspec = inspect.getargspec(func)
     first_arg = argspec[0][0]
     newargspec = (argspec[0][1:], ) + argspec[1:]
     fi = decorator.FunctionMaker(func)
     old_sig = inspect.formatargspec( \
         formatvalue=lambda val: "", *argspec)[1:-1]
     new_sig = inspect.formatargspec( \
         formatvalue=lambda val: "", *newargspec)[1:-1]
     new_def = '%s(%s)' % (fi.name, new_sig)
     body = 'return func(%s)' % old_sig
     def wrapper(*newargspec):
         return func(wrapper, *newargspec)
     evaldict = {'func':func, first_arg:wrapper}
     return fi.create(new_def, body, evaldict, \
         doc=fi.doc, module=fi.module)


I cannot remember whose sig it is at the moment (maybe Aahz'?), but it 
says something about debugging being twice as hard as coding, so if you 
code as cleverly as you can you are, by definition, not smart enough to 
debug it!  And considering the amount of trial-and-error that went into 
this (along with some thought and reasoning about scopes !-), I am 
hoping to get some code review.

And if there's an easier way to do this, I wouldn't mind knowing about 
that, too!

Many thanks.

~Ethan~



More information about the Python-list mailing list