在Python中设置函数签名

时间:2009-09-11 06:29:05

标签: python function

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

更多细节

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

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

即。假设我们有

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

def f(*args, **kwargs):
    #My code

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

def f2(x, y, opt=None):
    #My code

特定用例

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

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

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)

我们想在许多类中使用此函数。特别是,我们想要创建一个函数 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__)

我们希望create_initiation_function创建一个新功能,其中使用 init_args kw_init_args 定义签名。是否可以写create_initiation_function?

请注意:

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

相关

8 个答案:

答案 0 :(得分:22)

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

然后:

>>> @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)'

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

答案 1 :(得分:16)

对于你的用例,在类/函数中有一个docstring应该可以工作 - 这将显示在help()中,并且可以通过编程设置(func .__ doc__ =“stuff”)。

我看不出设置实际签名的方法。如果可行的话,我会认为functools module会做到这一点,但事实并非如此,至少在py2.5和py2.6中。

如果输入错误,也可以引发TypeError异常。

嗯,如果你不介意真的很卑鄙,你可以用compile()/ eval()来做。如果你想要的签名由arglist = [“foo”,“bar”,“baz”]指定,你的实际功能是f(* args,** kwargs),你可以管理:

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)

更改docstring和func_name应该可以为您提供完整的解决方案。但是,呃,... ...

答案 2 :(得分:5)

我写了一个package named forge来解决Python 3.5+的确切问题:

您当前的代码如下所示:

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

def f(*args, **kwargs):
    #My code

您想要的代码如下所示:

def f2(x, y, opt=None):
    #My code

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

f2 = forge.sign(
    forge.arg('x'),
    forge.arg('y'),
    forge.arg('opt', default=None),
)(f)

由于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})

答案 3 :(得分:1)

您不能使用实时代码执行此操作。

也就是说,您似乎想要采用看起来像这样的实际的实时函数:

def f(*args, **kwargs):
    print args[0]

并将其更改为:

def f(a):
    print a

不能这样做的原因 - 至少在没有修改实际Python字节码的情况下 - 是因为这些编译方式不同。

前者导致一个函数接收两个参数:list和dict,你正在编写的代码在该列表和dict上运行。第二个结果是一个函数接收一个参数,并直接作为局部变量访问。如果您更改了“签名”功能,可以说它会产生如下函数:

def f(a):
    print a[0]

显然不起作用。

如果你想要更多的细节(虽然它并没有真正帮助你),带* args或* kwargs的函数在f.func_code.co_flags中设置了一个或两个位;你可以自己检查一下。采用常规参数的函数将f.func_code.co_argcount设置为1; * args版本为0.这就是Python用来确定如何在调用函数时设置函数的堆栈帧,检查参数等等。

如果你想直接修改函数 - 如果只是为了让自己相信它不会起作用 - 请参阅this answer,了解如何创建代码对象和现有函数从现有函数到修改它的位。 (这个东西记录在某个地方,但我找不到它;它在类型模块文档中没有任何地方......)

也就是说,可以动态更改函数的文档字符串。只需分配到func.__doc__即可。一定要只在加载时执行此操作(从全局上下文或 - 最有可能 - 装饰器);如果你以后再这样做,加载模块来检查文档字符串的工具将永远不会看到它。

答案 4 :(得分:0)

也许我没有很好地理解这个问题,但是如果它是在改变函数签名时保持相同的行为,那么你可以做类似的事情:

# define a function
def my_func(name, age) :
    print "I am %s and I am %s" % (name, age)

# label the function with a backup name
save_func = my_func

# rewrite the function with a different signature
def my_func(age, name) :
    # use the backup name to use the old function and keep the old behavior
    save_func(name, age)

# you can use the new signature
my_func(35, "Bob")

输出:

I am Bob and I am 35

答案 5 :(得分:0)

看看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)

并按预期工作:

# 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'}

答案 6 :(得分:-3)

  

我们希望create_initiation_function更改签名

请不要这样做。

  

我们希望在许多类中使用此功能

请使用普通继承。

在运行时将签名“更改”没有任何价值。

你正在制造维护噩梦。没有人会费心去弄清楚你在做什么。他们只是把它撕掉并用继承代替它。

这样做。它简单明了,并且以明显,简单,Pythonic的方式使您的泛型init在所有子类中可用。

class Super( object ):
    def __init__( self, *args, **kwargs ):
        # the generic __init__ that we want every subclass to use

class SomeSubClass( Super ):
    def __init__( self, this, that, **kwdefaults ):
        super( SomeSubClass, self ).__init__( this, that, **kwdefaults )

class AnotherSubClass( Super ):
    def __init__( self, x, y, **kwdefaults ):
        super( AnotherSubClass, self ).__init__( x, y, **kwdefaults )

答案 7 :(得分:-5)

编辑1:回答新问题:

您问如何使用此签名创建函数:

def fun(a, b, opt=None):
    pass

因此,在Python中执行此操作的正确方法是:

def fun(a, b, opt=None):
    pass

编辑2:回答解释:

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

def f(*args, **kw):
    pass

好的,然后f2看起来像这样:

def f2(a, b, opt=None):
    f(a, b, opt=opt)

同样,你的问题的答案是如此微不足道,你显然想知道你所问的不同的东西。你确实需要停止提出抽象问题,并解释你的具体问题。