Very closely related to: How can I programmatically change the argspec of a function in a python decorator?
The decorator module provides the means to make a decorator function that preserves the argspec of the decorated function.
If I define a function that is not used as a decorator, is there a way to copy another function's argspec?
Example use case:
class Blah(object):
def foo(self, *args, **kwargs):
""" a docstr """
result = bar(*args, **kwargs)
result = result**2 # just so it's clear we're doing something extra here...
return result
def bar(x, y, z=1, q=2):
""" a more useful docstr, saying what x,y,z,q do """
return x+y*z+q
I would like to have foo
's argspec look like bar
's, but the source to stay unchanged (i.e., inspect.getsource(foo)
would still show the result
junk). The main purpose for this is to get sphinx docs and ipython's interactive help to show the appropriate arguments.
As the answers to the other question said, the decorator package shows a way to do this, but I got lost within the meat of that code. It seems that the decorator
package is recompiling the source, or something like that. I had hoped a simpler approach, e.g. something like foo.argspec = bar.argspec
, would be possible.
A decorator is simply a function that does something with another function. So, technically, you could put the required code directly underneath the foo
method and then, technically, you would be changing foo
without using a decorator, but it would be a horrible mess.
The easiest way to do what you want is going to be to make a decorator that takes a second function (bar
in this case) as an argument so it knows which signature to copy. The class code would then look something like:
class Blah(object):
@copy_argspec(bar)
def foo(self, *args, **kwargs):
""" a docstr """
result = bar(*args, **kwargs)
result = result**2 # just so it's clear we're doing something extra here...
return result
You'll have to have bar
defined before instead of after the class.
.
.
.
. . . time passes . . .
.
.
.
Okay, luckily I found an old decorator I could adapt.
help(Blah.foo)
looks like this before decoration:
Help on method foo in module __main__:
foo(self, *args, **kwargs) unbound __main__.Blah method
a docstr
and after decoration it looks like this:
Help on method foo in module __main__:
foo(self, x, y, z=1, q=2) unbound __main__.Blah method
a more useful docstr, saying what x,y,z,q do
Here's the decorator I used:
import inspect
class copy_argspec(object):
"""
copy_argspec is a signature modifying decorator. Specifically, it copies
the signature from `source_func` to the wrapper, and the wrapper will call
the original function (which should be using *args, **kwds). The argspec,
docstring, and default values are copied from src_func, and __module__ and
__dict__ from tgt_func.
"""
def __init__(self, src_func):
self.argspec = inspect.getargspec(src_func)
self.src_doc = src_func.__doc__
self.src_defaults = src_func.func_defaults
def __call__(self, tgt_func):
tgt_argspec = inspect.getargspec(tgt_func)
need_self = False
if tgt_argspec[0][0] == 'self':
need_self = True
name = tgt_func.__name__
argspec = self.argspec
if argspec[0][0] == 'self':
need_self = False
if need_self:
newargspec = (['self'] + argspec[0],) + argspec[1:]
else:
newargspec = argspec
signature = inspect.formatargspec(
formatvalue=lambda val: "",
*newargspec
)[1:-1]
new_func = (
'def _wrapper_(%(signature)s):\n'
' return %(tgt_func)s(%(signature)s)' %
{'signature':signature, 'tgt_func':'tgt_func'}
)
evaldict = {'tgt_func' : tgt_func}
exec new_func in evaldict
wrapped = evaldict['_wrapper_']
wrapped.__name__ = name
wrapped.__doc__ = self.src_doc
wrapped.func_defaults = self.src_defaults
wrapped.__module__ = tgt_func.__module__
wrapped.__dict__ = tgt_func.__dict__
return wrapped
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With