在Python中设置函数签名

Cas*_*ash 27 python function

假设我有一个通用函数f.我想以编程方式创建一个与f相同的函数f2,但它具有自定义签名.

更多详情

给定列表l和字典d我希望能够:

  • 将f2的非关键字参数设置为l中的字符串
  • 将f2的关键字参数设置为d中的键,将默认值设置为d的值

即.假设我们有

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code
Run Code Online (Sandbox Code Playgroud)

然后我想要一个带签名的函数:

def f2(x, y, opt=None):
    #My code
Run Code Online (Sandbox Code Playgroud)

一个特定的用例

这只是我特定用例的简化版本.我只是以此为例.

我的实际用例(简化)如下.我们有一个通用的启动函数:

def generic_init(self,*args,**kwargs):
    """Function to initiate a generic object"""
    for name, arg in zip(self.__init_args__,args):
        setattr(self, name, arg)
    for name, default in self.__init_kw_args__.items():
        if name in kwargs:
            setattr(self, name, kwargs[name])
        else:
            setattr(self, name, default)
Run Code Online (Sandbox Code Playgroud)

我们想在许多类中使用此函数.特别是,我们想要创建一个函数init,其行为类似于generic_init,但在创建时具有由某些类变量定义的签名:

class my_class:
    __init_args__=["x", "y"]
    __kw_init_args__={"my_opt": None}

__init__=create_initiation_function(my_class, generic_init)
setattr(myclass, "__init__", __init__)
Run Code Online (Sandbox Code Playgroud)

我们希望create_initiation_function创建一个新函数,其签名使用init_argskw_init_args定义.可以写create_initiation_function吗?

请注意:

  • 如果我只是想改进帮助,我可以设置doc.
  • 我们想在创建时设置功能签名.之后,它不需要更改.
  • 我们可以创建一个具有所需签名的新函数,而不是创建像generic_init这样的函数,但只需调用generic_init
  • 我们想要定义create_initiation_function.我们不想手动指定新功能!

有关

met*_*ure 26

PEP-0362开始,实际上似乎有一种方法可以使用以下fn.__signature__属性在py3.3 +中设置签名:

def shared_vars(*shared_args):
    """Decorator factory that defines shared variables that are
       passed to every invocation of the function"""

    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            full_args = shared_args + args
            return f(*full_args, **kwargs)

        # Override signature
        sig = signature(f)
        sig = sig.replace(parameters=tuple(sig.parameters.values())[1:])
        wrapper.__signature__ = sig

        return wrapper
    return decorator
Run Code Online (Sandbox Code Playgroud)

然后:

>>> @shared_vars({"myvar": "myval"})
>>> def example(_state, a, b, c):
>>>     return _state, a, b, c
>>> example(1,2,3)
({'myvar': 'myval'}, 1, 2, 3)
>>> str(signature(example))
'(a, b, c)'
Run Code Online (Sandbox Code Playgroud)

注意:PEP并不完全正确; Signature.replace将params从位置arg移动到kw-only arg.


Ant*_*wns 15

For your usecase, having a docstring in the class/function should work -- that will show up in help() okay, and can be set programmatically (func.__doc__ = "stuff").

I can't see any way of setting the actual signature. I would have thought the functools module would have done it if it was doable, but it doesn't, at least in py2.5 and py2.6.

You can also raise a TypeError exception if you get bad input.

Hmm, if you don't mind being truly vile, you can use compile()/eval() to do it. If your desired signature is specified by arglist=["foo","bar","baz"], and your actual function is f(*args,**kwargs), you can manage:

argstr = ", ".join(arglist)
fakefunc = "def func(%s):\n    return real_func(%s)\n" % (argstr, argstr)
fakefunc_code = compile(fakefunc, "fakesource", "exec")
fakeglobals = {}
eval(fakefunc_code, {"real_func": f}, fakeglobals)
f_with_good_sig = fakeglobals["func"]

help(f)               # f(*args, **kwargs)
help(f_with_good_sig) # func(foo, bar, baz)
Run Code Online (Sandbox Code Playgroud)

Changing the docstring and func_name should get you a complete solution. But, uh, eww...

  • 这是装饰包使用的模式 - http://pypi.python.org/pypi/decorator/ (7认同)
  • 我应该想到eval.一切皆有可能. (3认同)

Dev*_*vin 5

我写了一个名为forge Python 3.5+解决这个问题的软件包:

您当前的代码如下所示:

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code
Run Code Online (Sandbox Code Playgroud)

你想要的代码看起来像这样:

def f2(x, y, opt=None):
    #My code
Run Code Online (Sandbox Code Playgroud)

以下是使用以下方法解决的问题forge:

f2 = forge.sign(
    forge.arg('x'),
    forge.arg('y'),
    forge.arg('opt', default=None),
)(f)
Run Code Online (Sandbox Code Playgroud)

作为forge.sign包装器,您也可以直接使用它:

@forge.sign(
    forge.arg('x'),
    forge.arg('y'),
    forge.arg('opt', default=None),
)
def func(*args, **kwargs):
    # signature becomes: func(x, y, opt=None)
    return (args, kwargs)

assert func(1, 2) == ((), {'x': 1, 'y': 2, 'opt': None})
Run Code Online (Sandbox Code Playgroud)

  • 令人惊讶的是,尽管@Devin 几年前做了大量广告,但该软件包已不再维护。我很遗憾那些将这种依赖项添加到他们的项目中的人。 (8认同)

sma*_*rie 5

看一下makefun,它是为此而设计的(公开具有或多或少参数和准确签名的函数变体),并且适用于 python 2 和 3。

你的例子应该这样写:

try:  # python 3.3+
    from inspect import signature, Signature, Parameter
except ImportError:
    from funcsigs import signature, Signature, Parameter

from makefun import create_function

def create_initiation_function(cls, gen_init):
    # (1) check which signature we want to create
    params = [Parameter('self', kind=Parameter.POSITIONAL_OR_KEYWORD)]
    for mandatory_arg_name in cls.__init_args__:
        params.append(Parameter(mandatory_arg_name, kind=Parameter.POSITIONAL_OR_KEYWORD))
    for default_arg_name, default_arg_val in cls.__opt_init_args__.items():
        params.append(Parameter(default_arg_name, kind=Parameter.POSITIONAL_OR_KEYWORD, default=default_arg_val))
    sig = Signature(params)

    # (2) create the init function dynamically
    return create_function(sig, generic_init)

# ----- let's use it

def generic_init(self, *args, **kwargs):
    """Function to initiate a generic object"""
    assert len(args) == 0
    for name, val in kwargs.items():
        setattr(self, name, val)

class my_class:
    __init_args__ = ["x", "y"]
    __opt_init_args__ = {"my_opt": None}

my_class.__init__ = create_initiation_function(my_class, generic_init)
Run Code Online (Sandbox Code Playgroud)

并按预期工作:

# check 
o1 = my_class(1, 2)
assert vars(o1) == {'y': 2, 'x': 1, 'my_opt': None}

o2 = my_class(1, 2, 3)
assert vars(o2) == {'y': 2, 'x': 1, 'my_opt': 3}

o3 = my_class(my_opt='hello', y=3, x=2)
assert vars(o3) == {'y': 3, 'x': 2, 'my_opt': 'hello'}
Run Code Online (Sandbox Code Playgroud)