如何在python装饰器中以编程方式更改函数*不*的argspec?

kef*_*ich 6 python

与以下内容密切相关:如何编程方式更改python装饰器中函数的argspec?

装饰器模块提供了制作装饰器功能的方法,该功能保留了修饰函数的argspec.

如果我定义一个不用作装饰器的函数,有没有办法复制另一个函数的argspec?

用例示例:

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
Run Code Online (Sandbox Code Playgroud)

我希望fooargspec看起来像bar,但保持不变的来源(即inspect.getsource(foo)仍会显示result垃圾).这样做的主要目的是获取sphinx docs和ipython的交互式帮助以显示适当的参数.

正如另一个问题的答案所说,装饰包显示了一种方法,但我迷失了代码的内容.似乎decorator包正在重新编译源代码,或类似的东西.我原本希望有一种更简单的方法,例如类似的方法foo.argspec = bar.argspec.

Eth*_*man 6

装饰器只是一个与另一个函数一起做某事的函数。因此,从技术上讲,您可以将所需的代码直接放在foo方法下面,然后,从技术上讲,您可以在foo不使用装饰器的情况下进行更改,但这将是一团糟。

做你想做的最简单的方法是制作一个装饰器,它接受第二个函数(bar在这种情况下)作为参数,这样它就知道要复制哪个签名。类代码将如下所示:

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
Run Code Online (Sandbox Code Playgroud)

您必须bar在课程之前而不是之后定义。

.
.
.
. . . 时间过去了。. . .
.
.

好吧,幸运的是我找到了一个可以适应的旧装饰器。

help(Blah.foo) 装修前是这样的:

Help on method foo in module __main__:

foo(self, *args, **kwargs) unbound __main__.Blah method
    a docstr
Run Code Online (Sandbox Code Playgroud)

装饰后的样子是这样的:

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
Run Code Online (Sandbox Code Playgroud)

这是我使用的装饰器:

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
Run Code Online (Sandbox Code Playgroud)