假设我有一个通用函数f。我想以编程方式创建一个与f相同的函数f2,但它具有自定义签名。
更多细节
给定列表l和字典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?
请注意:
相关
答案 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)
同样,你的问题的答案是如此微不足道,你显然想知道你所问的不同的东西。你确实需要停止提出抽象问题,并解释你的具体问题。