简体   繁体   English

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

[英]How can I programmatically change the argspec of a function *not* in a python decorator?

Very closely related to: How can I programmatically change the argspec of a function in a python decorator? 与以下内容密切相关: 如何编程方式更改python装饰器中函数的argspec?

The decorator module provides the means to make a decorator function that preserves the argspec of the decorated function. 装饰器模块提供了制作装饰器功能的方法,该功能保留了修饰函数的argspec。

If I define a function that is not used as a decorator, is there a way to copy another function's argspec? 如果我定义一个不用作装饰器的函数,有没有办法复制另一个函数的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 (ie, inspect.getsource(foo) would still show the result junk). 我想让foo的argspec看起来像bar ,但保持不变的源(即inspect.getsource(foo)仍会显示result垃圾)。 The main purpose for this is to get sphinx docs and ipython's interactive help to show the appropriate arguments. 这样做的主要目的是获取sphinx docs和ipython的交互式帮助以显示适当的参数。

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. 似乎decorator包正在重新编译源,或类似的东西。 I had hoped a simpler approach, eg something like foo.argspec = bar.argspec , would be possible. 我曾希望有一种更简单的方法,比如foo.argspec = bar.argspec ,就可以了。

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. 因此,从技术上讲,您可以将所需的代码直接放在foo方法下面,然后从技术上讲,您将在不使用装饰器的情况下更改foo ,但这将是一个可怕的混乱。

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. 做你想做的最简单的方法是制作一个装饰器,它接受第二个函数(在这种情况下为bar )作为参数,因此它知道要复制哪个签名。 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. 您必须在之前定义bar而不是在课程之后定义bar

.
.
.
. . . time passes . 时间过去了 . . .
.
.

Okay, luckily I found an old decorator I could adapt. 好吧,幸运的是我发现了一个我可以适应的旧装饰。

help(Blah.foo) looks like this before decoration: help(Blah.foo)在装饰之前看起来像这样:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM