在 Python 中设置函数签名
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1409295/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Set function signature in Python
提问by Casebash
Suppose I have a generic function f
. I want to programmaticallycreate a function f2
that behaves the same as f
, but has a customized signature.
假设我有一个通用函数f
。我想以编程方式创建一个f2
与 行为相同f
但具有自定义签名的函数。
More detail
更多详情
Given a list l
and and dictionary d
I want to be able to:
给定一个列表l
和字典,d
我希望能够:
- Set the non-keyword arguments of
f2
to the strings inl
- Set the keyword arguments of
f2
to the keys ind
and the default values to the values ofd
- 将 的非关键字参数设置
f2
为中的字符串l
- 将 的关键字参数设置为
f2
键,d
并将默认值设置为d
ie. Suppose we have
IE。假设我们有
l = ["x", "y"]
d = {"opt": None}
def f(*args, **kwargs):
# My code
Then I would want a function with signature:
然后我想要一个带有签名的函数:
def f2(x, y, opt=None):
# My code
A specific use case
一个特定的用例
This is just a simplified version of my specific use case. I am giving this as an example only.
这只是我的特定用例的简化版本。我仅以此为例。
My actual use case (simplified) is as follows. We have a generic initiation function:
我的实际用例(简化)如下。我们有一个通用的启动函数:
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)
We want to use this function in a number of classes. In particular, we want to create a function __init__
that behaves like generic_init
, but has the signature defined by some class variables at creation time:
我们想在许多类中使用这个函数。特别是,我们想要创建一个__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__)
We want create_initiation_function to create a new function with the signature defined using __init_args__
and __kw_init_args__
. Is it possible to write create_initiation_function
?
我们希望 create_initiation_function 使用__init_args__
和定义的签名创建一个新函数__kw_init_args__
。可以写create_initiation_function
吗?
Please note:
请注意:
- If I just wanted to improve the help, I could set
__doc__
. - We want to set the function signature on creation. After that, it doesn't need to be changed.
- Instead of creating a function like
generic_init
, but with a different signature we could create a new function with the desired signature that just callsgeneric_init
- We want to define
create_initiation_function
. We don't want to manually specify the new function!
- 如果我只是想改进帮助,我可以设置
__doc__
. - 我们想在创建时设置函数签名。之后,它不需要更改。
generic_init
我们可以创建一个具有所需签名的新函数,而不是创建一个类似的函数,但使用不同的签名generic_init
- 我们要定义
create_initiation_function
. 我们不想手动指定新功能!
Related
有关的
- Preserving signatures of decorated functions: This is how to preserve a signature when decorating a function. We need to be able to set the signature to an arbitrary value
采纳答案by Anthony Towns
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").
对于您的用例,在类/函数中有一个文档字符串应该可以工作——它会出现在 help() 中,并且可以以编程方式设置 (func.__doc__ = "stuff")。
I can't see any way of setting the actual signature. I would have thought the functools modulewould have done it if it was doable, but it doesn't, at least in py2.5 and py2.6.
我看不到任何设置实际签名的方法。如果可行,我会认为functools 模块会完成它,但它没有,至少在 py2.5 和 py2.6 中。
You can also raise a TypeError exception if you get bad input.
如果输入错误,您还可以引发 TypeError 异常。
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:
嗯,如果你不介意真正卑鄙,你可以使用 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)
Changing the docstring and func_name should get you a complete solution. But, uh, eww...
更改 docstring 和 func_name 应该可以为您提供完整的解决方案。但是,呃,呃...
回答by metaperture
From PEP-0362, there actually does appear to be a way to set the signature in py3.3+, using the fn.__signature__
attribute:
从PEP-0362 来看,实际上似乎有一种方法可以使用以下fn.__signature__
属性在 py3.3+ 中设置签名:
from inspect import signature
from functools import wraps
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
Then:
然后:
>>> @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)'
Note: the PEP is not exactly right; Signature.replace moved the params from a positional arg to a kw-only arg.
注意:PEP 并不完全正确;Signature.replace 将参数从位置 arg 移动到仅限 kw 的 arg。
回答by Devin
I wrote a package named forge
that solves this exact problem for Python 3.5+:
我写了一个名为forge
的包,它解决了 Python 3.5+ 的这个确切问题:
With your current code looking like this:
您当前的代码如下所示:
l=["x", "y"]
d={"opt":None}
def f(*args, **kwargs):
#My code
And your desired code looking like this:
您想要的代码如下所示:
def f2(x, y, opt=None):
#My code
Here is how you would solve that using forge
:
以下是您将如何使用以下方法解决该问题forge
:
f2 = forge.sign(
forge.arg('x'),
forge.arg('y'),
forge.arg('opt', default=None),
)(f)
As forge.sign
is a wrapper, you could also use it directly:
作为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})
回答by smarie
Have a look at makefun
, it was made for that (exposing variants of functions with more or less parameters and accurate signature), and works in python 2 and 3.
看看makefun
,它是为此而制作的(暴露具有或多或少参数和准确签名的函数变体),并在 python 2 和 3 中工作。
Your example would be written like this:
你的例子会写成这样:
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)
and works as expected:
并按预期工作:
# 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'}
回答by Glenn Maynard
You can't do this with live code.
你不能用实时代码做到这一点。
That is, you seem to be wanting to take an actual, live function that looks like this:
也就是说,您似乎想要采用如下所示的实际实时函数:
def f(*args, **kwargs):
print args[0]
and change it to one like this:
并将其更改为这样的:
def f(a):
print a
The reason this can't be done--at least without modifying actual Python bytecode--is because these compile differently.
无法做到这一点的原因——至少在不修改实际 Python 字节码的情况下——是因为它们的编译方式不同。
The former results in a function that receives two parameters: a list and a dict, and the code you're writing operates on that list and dict. The second results in a function that receives one parameter, and which is accessed as a local variable directly. If you changed the function "signature", so to speak, it'd result in a function like this:
前者产生一个接收两个参数的函数:一个列表和一个 dict,您正在编写的代码对该列表和 dict 进行操作。第二个结果是一个函数,它接收一个参数,并直接作为局部变量访问。如果你改变了函数“签名”,可以这么说,它会产生这样的函数:
def f(a):
print a[0]
which obviously wouldn't work.
这显然行不通。
If you want more detail (though it doesn't really help you), a function that takes an *args or *kwargs has one or two bits set in f.func_code.co_flags
; you can examine this yourself. The function that takes a regular parameter has f.func_code.co_argcount
set to 1; the *args version is 0. This is what Python uses to figure out how to set up the function's stack frame when it's called, to check parameters, etc.
如果您想了解更多细节(尽管它对您没有真正的帮助),一个带有 *args 或 *kwargs 的函数会在f.func_code.co_flags
; 中设置一两个位;你可以自己检查一下。采用常规参数的函数已f.func_code.co_argcount
设置为 1;*args 版本是 0。这是 Python 用来确定在调用函数时如何设置函数的堆栈帧、检查参数等的方法。
If you want to play around with modifying the function directly--if only to convince yourself that it won't work--see this answerfor how to create a code object and live function from an existing one to modify bits of it. (This stuff is documented somewhere, but I can't find it; it's nowhere in the types module docs...)
如果您想直接修改函数——如果只是为了让自己相信它不起作用——请参阅此答案以了解如何从现有对象创建代码对象和实时函数以修改其中的位。(这个东西在某处有文档记录,但我找不到它;它在类型模块文档中无处可见......)
That said, you candynamically change the docstring of a function. Just assign to func.__doc__
. Be sure to only do this at load time (from the global context or--most likely--a decorator); if you do it later on, tools that load the module to examine docstrings will never see it.
也就是说,您可以动态更改函数的文档字符串。只需分配给func.__doc__
. 确保仅在加载时执行此操作(来自全局上下文或 - 很可能 - 装饰器);如果您稍后这样做,则加载模块以检查文档字符串的工具将永远不会看到它。
回答by e-satis
Maybe I didn't understand the problem well, but if it's about keeping the same behavior while changing the function signature, then you can do something like :
也许我没有很好地理解这个问题,但是如果是在更改函数签名时保持相同的行为,那么您可以执行以下操作:
# 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")
This outputs :
这输出:
I am Bob and I am 35
回答by S.Lott
We want
create_initiation_function
to change the signature
我们要
create_initiation_function
更改签名
Please don't do this.
请不要这样做。
We want to use this function in a number of classes
我们想在许多类中使用这个函数
Please use ordinary inheritance.
请使用普通继承。
There's no value in having the signature "changed" at run time.
在运行时“更改”签名没有任何价值。
You're creating a maintenance nightmare. No one else will ever bother to figure out what you're doing. They'll simply rip it out and replace it with inheritance.
您正在制造维护噩梦。没有人会费心去弄清楚你在做什么。他们会简单地将其撕掉并用继承取而代之。
Do this instead. It's simple and obvious and makes your generic init available in all subclasses in an obvious, simple, Pythonic way.
改为这样做。它简单明了,并以一种明显、简单、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 )
回答by Lennart Regebro
Edit 1: Answering new question:
编辑 1:回答新问题:
You ask how you can create a function with this signature:
您询问如何使用此签名创建函数:
def fun(a, b, opt=None):
pass
The correct way to do that in Python is thus:
因此,在 Python 中执行此操作的正确方法是:
def fun(a, b, opt=None):
pass
Edit 2: Answering explanation:
编辑2:回答解释:
"Suppose I have a generic function f. I want to programmatically create a function f2 that behaves the same as f, but has a customised signature."
“假设我有一个通用函数 f。我想以编程方式创建一个函数 f2,它的行为与 f 相同,但具有自定义签名。”
def f(*args, **kw):
pass
OK, then f2 looks like so:
好的,那么 f2 看起来像这样:
def f2(a, b, opt=None):
f(a, b, opt=opt)
Again, the answer to your question is so trivial, that you obviously want to know something different that what you are asking. You really do need to stop asking abstract questions, and explain your concrete problem.
同样,你的问题的答案是如此微不足道,你显然想知道一些与你所问的不同的东西。你真的需要停止问抽象的问题,并解释你的具体问题。