Python 3.6
我正在尝试创建一个装饰器,它自动将参数的字符串指定为默认值。
如:
def example(one='one', two='two', three='three'):
pass
相当于:
@DefaultArguments
def example(one, two, three):
pass
这是我的尝试(但不起作用......)DefaultArguments
:
from inspect import Parameter, Signature, signature
class DefaultArguments(object):
@staticmethod
def default_signature(signature):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
return param.replace(default=param.name)
else:
return param
return Signature([default(param) for param in signature.parameters.values()])
def __init__(self, func):
self.func = func
self.sig = self.default_signature(signature(func))
def __call__(self, *args, **kwargs):
arguments = self.sig.bind(*args, **kwargs)
return self.func(arguments)
staticmethod default_signature
为函数创建了所需的签名,但是我很难将新签名分配给函数。我正在尝试使用签名。bind
我已阅读docs,但我遗失了一些内容。
编辑
纳入Ashwini Chaudhary的回答:
from inspect import Parameter, Signature, signature
class DefaultArguments(object):
@staticmethod
def default_signature(signature):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
return param.replace(default=param.name)
else:
return param
return Signature([default(param) for param in signature.parameters.values()])
def __init__(self, func):
self.func = func
self.sig = self.default_signature(signature(func))
print(self.sig)
def __call__(self, *args, **kwargs):
ba = self.sig.bind(*args, **kwargs)
ba.apply_defaults()
return self.func(*ba.args, **ba.kwargs)
答案 0 :(得分:5)
这似乎有效:
import inspect
def default_args(func):
argspec = inspect.getfullargspec(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
unpassed_positional_args = argspec.args[len(args):]
kwargs.update((a, a) for a in unpassed_positional_args if a not in kwargs)
return func(*args, **kwargs)
return wrapper
它依赖于你可以在python中通过关键字传递位置参数这一事实。例如如果你有一个功能:
def foo(a, b):
...
您完全有权将其称为:
foo(b=1, a=2)
我的解决方案会计算出您已经通过了多少位置参数,并使用它来确定哪些位置参数没有通过。然后我将这些位置参数名称添加到kwargs
dict中。
这里很酷的是,如果有人需要这个用于python2.x,他们只需要将getfullargspec
更改为getargspec
,它应该可以正常工作。
将我的解决方案与Ashwini的优秀解释进行比较表明,简单的装饰器比使用Signature对象快了大约10倍:
@default_args
def foo(a, b, c):
pass
@DefaultArguments
def bar(a, b, c):
pass
@default_arguments
def qux(a, b, c):
pass
import timeit
print(timeit.timeit('foo()', 'from __main__ import foo')) # 1.72s
print(timeit.timeit('bar()', 'from __main__ import bar')) # 17.4s
print(timeit.timeit('qux()', 'from __main__ import qux')) # 17.6
他的解决方案实际上更新了函数的__signature__
(非常好)。原则上,您可以采用Signature
创建逻辑并将其添加到我的解决方案中以更新__signature__
但保留实际计算的argspec
样式逻辑...
答案 1 :(得分:3)
将args和关键字与签名绑定后,您需要在apply_defaults
实例上调用BoundArguments
来设置缺少参数的默认值。
此外,还将使用BoundArguments
的{{1}}和args
属性调用函数调用。
kwargs
<强>演示:强>
def __call__(self, *args, **kwargs):
ba = self.sig.bind(*args, **kwargs)
ba.apply_defaults()
return self.func(*ba.args, **ba.kwargs)
代码的功能版本,它还会更新已修饰函数的签名:
>>> @DefaultArguments
... def example(one, two, three):
... print(one, two, three)
...
>>> example()
one two three
>>> example('spam')
spam two three
>>> example(one='spam', three='eggs')
spam two eggs
<强>演示:强>
from functools import wraps
from inspect import Parameter, Signature, signature
def default_arguments(func):
def default(param):
if param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
param = param.replace(default=param.name)
return param
sig = Signature([default(param) for param in signature(func).parameters.values()])
@wraps(func)
def wrapper(*args, **kwargs):
ba = sig.bind(*args, **kwargs)
ba.apply_defaults()
return func(*ba.args, **ba.kwargs)
wrapper.__signature__ = sig
return wrapper