如何在 Python 2.7 中向包装函数添加关键字参数?
How can I add keyword arguments to a wrapped function in Python 2.7?
我首先想强调的是,我已经广泛地搜索了网络和 Python 文档 + Whosebug,但没有设法找到这个问题的答案。我还要感谢所有花时间阅读本文的人。
如标题所示,我正在 Python 中编写装饰器,我希望它向包装函数添加关键字参数(请注意:我知道如何向装饰器本身添加参数,这不是我要问的)。
这是我编写的一段代码的 工作示例 ,它完全适用于 Python 3(特别是 Python 3.5)。它使用装饰器参数,向包装函数添加关键字参数,还定义并向包装函数添加新函数。
from functools import wraps
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# Do Something 1
@wraps(func)
def func_wrapper(
*args,
new_arg1=False,
new_arg2=None,
**kwds):
# Inside the wrapping function
# Calling the wrapped function
if new_arg1:
return func(*args, **kwds)
else:
# do something with new_arg2
return func(*args, **kwds)
def added_function():
print("Do Something 2")
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
现在这个装饰器可以按以下方式使用:
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
print("a={}, b={}".format(a,b))
def bar():
foo(a=1, b=2, new_arg1=True, new_arg2=7)
foo.added_function()
现在,虽然这适用于 Python 3.5(我假设适用于任何 3.x),但我无法使其适用于 Python 2.7。我在第一行得到一个 SyntaxError: invalid syntax
试图为 func_wrapper
定义一个新的关键字参数,意思是在导入包含此代码的模块时说明 new_arg1=False,
的行。
将新关键字移动到 func_wrapper
参数列表的开头解决了 SyntaxError
但似乎与包装函数的签名有关;我现在在调用 foo(1, 2)
时遇到错误 TypeError: foo() takes exactly 2 arguments (0 given)
。如果我明确地分配参数,这个错误就会消失,如 foo(a=1, b=2)
,但这显然是不够的——不出所料,我的新关键字参数似乎是 "stealing" 发送到包装函数的前两个位置参数。 Python 3.
没有发生这种情况
我很乐意得到你的帮助。感谢您花时间阅读本文。
谢伊
如果您只将附加参数指定为关键字,则可以将它们从 kw 字典中取出(见下文)。如果您需要它们作为位置 AND 关键字参数,那么我认为您应该能够在原始函数上使用 inspect.getargspec,然后在 [=21= 中处理 args 和 kw ].
下面的代码在 Ubuntu 14.04 和 Python 2.7、3.4(均由 Ubuntu 提供)和 3.5(来自 Continuum)上进行了测试。
from functools import wraps
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# Do Something 1
@wraps(func)
def func_wrapper(
*args,
**kwds):
# new_arg1, new_arg2 *CANNOT* be positional args with this technique
new_arg1 = kwds.pop('new_arg1',False)
new_arg2 = kwds.pop('new_arg2',None)
# Inside the wrapping function
# Calling the wrapped function
if new_arg1:
print("new_arg1 True branch; new_arg2 is {}".format(new_arg2))
return func(*args, **kwds)
else:
print("new_arg1 False branch; new_arg2 is {}".format(new_arg2))
# do something with new_arg2
return func(*args, **kwds)
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
print("a={}, b={}".format(a,b))
def bar():
pass
#foo(1,2,True,7) # won't work
foo(1, 2, new_arg1=True, new_arg2=7)
foo(a=3, b=4, new_arg1=False, new_arg2=42)
foo(new_arg2=-1,b=100,a='AAA')
foo(b=100,new_arg1=True,a='AAA')
foo.added_function()
if __name__=='__main__':
import sys
sys.stdout.flush()
bar()
输出为
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function
要向现有函数的签名添加参数,同时使该函数表现得像普通的 python 函数(正确的帮助、签名和 TypeError
在提供错误参数的情况下引发),您可以使用makefun
,我专门开发它来解决这个用例。
特别是 makefun
提供了 @wraps
的替代品,它有一个 new_sig
参数,您可以在其中指定新签名。这是您的示例的写法:
try: # python 3.3+
from inspect import signature, Parameter
except ImportError:
from funcsigs import signature, Parameter
from makefun import wraps, add_signature_parameters
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# (1) capture the signature of the function to wrap ...
func_sig = signature(func)
# ... and modify it to add new optional parameters 'new_arg1' and 'new_arg2'.
# (if they are optional that's where you provide their defaults)
new_arg1 = Parameter('new_arg1', kind=Parameter.POSITIONAL_OR_KEYWORD, default=False)
new_arg2 = Parameter('new_arg2', kind=Parameter.POSITIONAL_OR_KEYWORD, default=None)
new_sig = add_signature_parameters(func_sig, last=[new_arg1, new_arg2])
# (2) create a wrapper with the new signature
@wraps(func, new_sig=new_sig)
def func_wrapper(*args, **kwds):
# Inside the wrapping function
# Pop the extra args (they will always be there, no need to provide default)
new_arg1 = kwds.pop('new_arg1')
new_arg2 = kwds.pop('new_arg2')
# Calling the wrapped function
if new_arg1:
print("new_arg1 True branch; new_arg2 is {}".format(new_arg2))
return func(*args, **kwds)
else:
print("new_arg1 False branch; new_arg2 is {}".format(new_arg2))
# do something with new_arg2
return func(*args, **kwds)
# (3) add an attribute to the wrapper
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
"""This is my foo function"""
print("a={}, b={}".format(a,b))
foo(1, 2, True, 7) # works, except if you use kind=Parameter.KEYWORD_ONLY above (py3 only)
foo(1, 2, new_arg1=True, new_arg2=7)
foo(a=3, b=4, new_arg1=False, new_arg2=42)
foo(new_arg2=-1,b=100,a='AAA')
foo(b=100,new_arg1=True,a='AAA')
foo.added_function()
help(foo)
它如您所愿地工作:
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function
Help on function foo in module <...>:
foo(a, b, new_arg1=False, new_arg2=None)
This is my foo function
所以你可以看到暴露的签名符合预期,你的用户看不到内部结构。请注意,您可以通过在新签名中设置 kind=Parameter.KEYWORD_ONLY
来使两个新参数“仅限关键字”,但正如您已经知道的那样,这在 python 2.
中不起作用
最后,您可能有兴趣使用 decopatch
使您的装饰器代码更具可读性和对无括号用法的鲁棒性。除其他外,它支持非常适合您的情况的“平面”样式,因为它删除了一层嵌套:
from decopatch import function_decorator, DECORATED
@function_decorator
def my_decorator(decorator_arg1=None, decorator_arg2=False, func=DECORATED):
# (1) capture the signature of the function to wrap ...
func_sig = signature(func)
# ...
# (2) create a wrapper with the new signature
@wraps(func, new_sig=new_sig)
def func_wrapper(*args, **kwds):
# Inside the wrapping function
...
# (3) add an attribute to the wrapper
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
(我也是这个的作者,因为厌倦了嵌套和无括号的处理而创建了它)
我首先想强调的是,我已经广泛地搜索了网络和 Python 文档 + Whosebug,但没有设法找到这个问题的答案。我还要感谢所有花时间阅读本文的人。
如标题所示,我正在 Python 中编写装饰器,我希望它向包装函数添加关键字参数(请注意:我知道如何向装饰器本身添加参数,这不是我要问的)。
这是我编写的一段代码的 工作示例 ,它完全适用于 Python 3(特别是 Python 3.5)。它使用装饰器参数,向包装函数添加关键字参数,还定义并向包装函数添加新函数。
from functools import wraps
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# Do Something 1
@wraps(func)
def func_wrapper(
*args,
new_arg1=False,
new_arg2=None,
**kwds):
# Inside the wrapping function
# Calling the wrapped function
if new_arg1:
return func(*args, **kwds)
else:
# do something with new_arg2
return func(*args, **kwds)
def added_function():
print("Do Something 2")
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
现在这个装饰器可以按以下方式使用:
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
print("a={}, b={}".format(a,b))
def bar():
foo(a=1, b=2, new_arg1=True, new_arg2=7)
foo.added_function()
现在,虽然这适用于 Python 3.5(我假设适用于任何 3.x),但我无法使其适用于 Python 2.7。我在第一行得到一个 SyntaxError: invalid syntax
试图为 func_wrapper
定义一个新的关键字参数,意思是在导入包含此代码的模块时说明 new_arg1=False,
的行。
将新关键字移动到 func_wrapper
参数列表的开头解决了 SyntaxError
但似乎与包装函数的签名有关;我现在在调用 foo(1, 2)
时遇到错误 TypeError: foo() takes exactly 2 arguments (0 given)
。如果我明确地分配参数,这个错误就会消失,如 foo(a=1, b=2)
,但这显然是不够的——不出所料,我的新关键字参数似乎是 "stealing" 发送到包装函数的前两个位置参数。 Python 3.
我很乐意得到你的帮助。感谢您花时间阅读本文。
谢伊
如果您只将附加参数指定为关键字,则可以将它们从 kw 字典中取出(见下文)。如果您需要它们作为位置 AND 关键字参数,那么我认为您应该能够在原始函数上使用 inspect.getargspec,然后在 [=21= 中处理 args 和 kw ].
下面的代码在 Ubuntu 14.04 和 Python 2.7、3.4(均由 Ubuntu 提供)和 3.5(来自 Continuum)上进行了测试。
from functools import wraps
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# Do Something 1
@wraps(func)
def func_wrapper(
*args,
**kwds):
# new_arg1, new_arg2 *CANNOT* be positional args with this technique
new_arg1 = kwds.pop('new_arg1',False)
new_arg2 = kwds.pop('new_arg2',None)
# Inside the wrapping function
# Calling the wrapped function
if new_arg1:
print("new_arg1 True branch; new_arg2 is {}".format(new_arg2))
return func(*args, **kwds)
else:
print("new_arg1 False branch; new_arg2 is {}".format(new_arg2))
# do something with new_arg2
return func(*args, **kwds)
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
print("a={}, b={}".format(a,b))
def bar():
pass
#foo(1,2,True,7) # won't work
foo(1, 2, new_arg1=True, new_arg2=7)
foo(a=3, b=4, new_arg1=False, new_arg2=42)
foo(new_arg2=-1,b=100,a='AAA')
foo(b=100,new_arg1=True,a='AAA')
foo.added_function()
if __name__=='__main__':
import sys
sys.stdout.flush()
bar()
输出为
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function
要向现有函数的签名添加参数,同时使该函数表现得像普通的 python 函数(正确的帮助、签名和 TypeError
在提供错误参数的情况下引发),您可以使用makefun
,我专门开发它来解决这个用例。
特别是 makefun
提供了 @wraps
的替代品,它有一个 new_sig
参数,您可以在其中指定新签名。这是您的示例的写法:
try: # python 3.3+
from inspect import signature, Parameter
except ImportError:
from funcsigs import signature, Parameter
from makefun import wraps, add_signature_parameters
def my_decorator(decorator_arg1=None, decorator_arg2=False):
# Inside the wrapper maker
def _decorator(func):
# (1) capture the signature of the function to wrap ...
func_sig = signature(func)
# ... and modify it to add new optional parameters 'new_arg1' and 'new_arg2'.
# (if they are optional that's where you provide their defaults)
new_arg1 = Parameter('new_arg1', kind=Parameter.POSITIONAL_OR_KEYWORD, default=False)
new_arg2 = Parameter('new_arg2', kind=Parameter.POSITIONAL_OR_KEYWORD, default=None)
new_sig = add_signature_parameters(func_sig, last=[new_arg1, new_arg2])
# (2) create a wrapper with the new signature
@wraps(func, new_sig=new_sig)
def func_wrapper(*args, **kwds):
# Inside the wrapping function
# Pop the extra args (they will always be there, no need to provide default)
new_arg1 = kwds.pop('new_arg1')
new_arg2 = kwds.pop('new_arg2')
# Calling the wrapped function
if new_arg1:
print("new_arg1 True branch; new_arg2 is {}".format(new_arg2))
return func(*args, **kwds)
else:
print("new_arg1 False branch; new_arg2 is {}".format(new_arg2))
# do something with new_arg2
return func(*args, **kwds)
# (3) add an attribute to the wrapper
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
return _decorator
@my_decorator(decorator_arg1=4, decorator_arg2=True)
def foo(a, b):
"""This is my foo function"""
print("a={}, b={}".format(a,b))
foo(1, 2, True, 7) # works, except if you use kind=Parameter.KEYWORD_ONLY above (py3 only)
foo(1, 2, new_arg1=True, new_arg2=7)
foo(a=3, b=4, new_arg1=False, new_arg2=42)
foo(new_arg2=-1,b=100,a='AAA')
foo(b=100,new_arg1=True,a='AAA')
foo.added_function()
help(foo)
它如您所愿地工作:
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 True branch; new_arg2 is 7
a=1, b=2
new_arg1 False branch; new_arg2 is 42
a=3, b=4
new_arg1 False branch; new_arg2 is -1
a=AAA, b=100
new_arg1 True branch; new_arg2 is None
a=AAA, b=100
added_function
Help on function foo in module <...>:
foo(a, b, new_arg1=False, new_arg2=None)
This is my foo function
所以你可以看到暴露的签名符合预期,你的用户看不到内部结构。请注意,您可以通过在新签名中设置 kind=Parameter.KEYWORD_ONLY
来使两个新参数“仅限关键字”,但正如您已经知道的那样,这在 python 2.
最后,您可能有兴趣使用 decopatch
使您的装饰器代码更具可读性和对无括号用法的鲁棒性。除其他外,它支持非常适合您的情况的“平面”样式,因为它删除了一层嵌套:
from decopatch import function_decorator, DECORATED
@function_decorator
def my_decorator(decorator_arg1=None, decorator_arg2=False, func=DECORATED):
# (1) capture the signature of the function to wrap ...
func_sig = signature(func)
# ...
# (2) create a wrapper with the new signature
@wraps(func, new_sig=new_sig)
def func_wrapper(*args, **kwds):
# Inside the wrapping function
...
# (3) add an attribute to the wrapper
def added_function():
# Do Something 2
print('added_function')
func_wrapper.added_function = added_function
return func_wrapper
(我也是这个的作者,因为厌倦了嵌套和无括号的处理而创建了它)