Preserving the argspec of a function after generating a closure

Bengt Richter bokr at oz.net
Fri Mar 11 15:36:32 EST 2005


On Fri, 11 Mar 2005 11:55:51 -0500, Victor Ng <crankycoder at gmail.com> wrote:

>Is there a way to preserve the argspec of a function after wrapping it
>in a closure?
>
>I'm looking for a general way to say "wrap function F in a closure",
>such that inspect.getargspec on the closure would return the same
>(args, varargs, varkw, defaults)  tuple ass the enclosed function.
>
>The typical code I'm using is something like this:
>
>def wrapFunc(func):
>    def tmpWrapper(*args, **kwargs):
>        return func(*args, **kwargs)
>    tmpWrapper.func_name = func.func_name
>    return wrapFunc
>
>This preserves the function name - how do I do more?
Probably you could use the inspect.getargspec(func) info to
build a wrapper with the identical signature that would call func
passing everything through 1:1, but that would seem kind of useless, unless
you want to substitute some closure values into the func call and get
a currying effect. In that case, your new signature should probably be changed
to reflect the real effective signature of the curried function.

I recently posted a byte-code-munging decorator hack that accomplishes some of that. E.g.,
(I keep presets.py in my ut package, where I accumulate miscellaneous experimental untility stuff)

 >>> from ut.presets import presets, curry
 >>> @curry(y=123)
 ... def foo(x, y): return x*y
 ...
 >>> import inspect
 >>> inspect.getargspec(foo)
 (['x'], None, None, None)
 >>> import dis
 >>> dis.dis(foo)
   1           0 LOAD_CONST               1 (123)
               3 STORE_FAST               1 (y)

   3           6 LOAD_FAST                0 (x)
               9 LOAD_FAST                1 (y)
              12 BINARY_MULTIPLY
              13 RETURN_VALUE

And without the currying decorator:

 >>> def foo(x, y): return x*y
 ...
 >>> inspect.getargspec(foo)
 (['x', 'y'], None, None, None)
 >>> dis.dis(foo)
   1           0 LOAD_FAST                0 (x)
               3 LOAD_FAST                1 (y)
               6 BINARY_MULTIPLY
               7 RETURN_VALUE


Basically, the curry decorator here injects a local assignment and adjusts the line map
and signature and a few other tweaks, depending.

This doesn't wrap the function though, it modifies it.
I haven't tried decorating indirectly... Hm...
Faking decoration of foo with getf to substitute the func passed ...

 >>> def wrap(func, y):
 ...     def getf(f): return func
 ...     @curry(y=y)
 ...     @getf
 ...     def foo(): pass
 ...     foo.func_name = func.func_name
 ...     return foo
 ...
 >>> def bar(x, y): return x+y
 ...
 >>> wbar = wrap(bar, 1000)
 >>> wbar
 <function bar at 0x02F1AAE4>
 >>> bar(123)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: bar() takes exactly 2 arguments (1 given)

oops, that should have been wbar ...

 >>> wbar(123)
 1123
 >>> inspect.getargspec(wbar)
 (['x'], None, None, None)
 >>> import dis
 >>> dis.dis(wbar)
   1           0 LOAD_CONST               1 (1000)
               3 STORE_FAST               1 (y)

   3           6 LOAD_FAST                0 (x)
               9 LOAD_FAST                1 (y)
              12 BINARY_ADD
              13 RETURN_VALUE

 >>> inspect.getargspec(bar)
 (['x', 'y'], None, None, None)
 >>> dis.dis(bar)
   1           0 LOAD_FAST                0 (x)
               3 LOAD_FAST                1 (y)
               6 BINARY_ADD
               7 RETURN_VALUE

I don't know where this is leading. What was your goal again?

Regards,
Bengt Richter



More information about the Python-list mailing list