我是python和decorators的新手,我很难写一个装饰器,它不仅报告了args和kwargs,还报告了默认的默认kwargs。
这是我到目前为止所做的。
def document_call(fn):
def wrapper(*args, **kwargs):
print 'function %s called with positional args %s and keyword args %s' % (fn.__name__, args, kwargs)
return fn(*args, **kwargs)
return wrapper
@document_call
def square(n, trial=True, output=False):
# kwargs are a bit of nonsense to test function
if not output:
print 'no output'
if trial:
print n*n
square(6) # with this call syntax, the default kwargs are not reported
# function square called with positional args (6,) and keyword args {}
# no output
36
square(7,output=True) # only if a kwarg is changed from default is it reported
# function square called with positional args (7,) and keyword args {'output': True}
49
'问题'是这个装饰器报告在方形调用中传递的args,但不报告方形定义中定义的默认kwargs。报告kwargs的唯一方法是,如果他们从默认值更改,即传递给方形呼叫。
关于我如何获得方形定义中的kwargs的任何建议也被报道?
在跟进检查建议后进行编辑,这有助于我解决下面的问题。我更改了位置参数的输出以包含它们的名称,因为我认为它使输出更容易理解。
import inspect
def document_call(fn):
def wrapper(*args, **kwargs):
argspec = inspect.getargspec(fn)
n_postnl_args = len(argspec.args) - len(argspec.defaults)
# get kwargs passed positionally
passed = {k:v for k,v in zip(argspec.args[n_postnl_args:], args[n_postnl_args:])}
# update with kwargs
passed.update({k:v for k,v in kwargs.iteritems()})
print 'function %s called with \n positional args %s\n passed kwargs %s\n default kwargs %s' % (
fn.__name__, {k:v for k,v in zip(argspec.args, args[:n_postnl_args])},
passed,
{k:v for k,v in zip(argspec.args[n_postnl_args:], argspec.defaults) if k not in passed})
return fn(*args, **kwargs)
return wrapper
这是一次很好的学习经历。看到同一问题的三种不同解决方案是很好的。感谢Answerers!
答案 0 :(得分:6)
您必须反省自己包装的功能,才能阅读默认设置。您可以使用inspect.getargspec()
function。
该函数返回一个元组,其中包含所有参数名称的序列和一系列默认值。最后一个参数名称与默认值配对以形成名称 - 默认对;您可以使用它来创建字典并从那里提取未使用的默认值:
import inspect
argspec = inspect.getargspec(fn)
positional_count = len(argspec.args) - len(argspec.defaults)
defaults = dict(zip(argspec.args[positional_count:], argspec.defaults))
你需要考虑位置参数 也可以指定默认参数,因此找出关键字参数的舞蹈更复杂但看起来像这样:
def document_call(fn):
argspec = inspect.getargspec(fn)
positional_count = len(argspec.args) - len(argspec.defaults)
defaults = dict(zip(argspec.args[positional_count:], argspec.defaults))
def wrapper(*args, **kwargs):
used_kwargs = kwargs.copy()
used_kwargs.update(zip(argspec.args[positional_count:], args[positional_count:]))
print 'function %s called with positional args %s and keyword args %s' % (
fn.__name__, args[:positional_count],
{k: used_kwargs.get(k, d) for k, d in defaults.items()})
return fn(*args, **kwargs)
return wrapper
这确定了传入的位置参数和关键字参数实际使用了哪些关键字参数,然后为未使用的参数拉出默认值。
演示:
>>> square(39)
function square called with positional args (39,) and keyword args {'trial': True, 'output': False}
no output
1521
>>> square(39, False)
function square called with positional args (39,) and keyword args {'trial': False, 'output': False}
no output
>>> square(39, False, True)
function square called with positional args (39,) and keyword args {'trial': False, 'output': True}
>>> square(39, False, output=True)
function square called with positional args (39,) and keyword args {'trial': False, 'output': True}
答案 1 :(得分:4)
从Python 3.5开始,您可以使用BoundArguments.apply_defaults
用默认值填充缺少的参数:
import inspect
def document_call(fn):
def wrapper(*args, **kwargs):
bound = inspect.signature(fn).bind(*args, **kwargs)
bound.apply_defaults()
print(f'{fn.__name__} called with {bound}')
return fn(*args, **kwargs)
return wrapper
答案 2 :(得分:1)
由于装饰器函数wrapper
接受任何参数并且只是传递了所有内容,当然它对包装函数的参数及其默认值一无所知。
因此,如果没有实际查看装饰功能,您将无法获得此信息。幸运的是,您可以使用inspect模块找出包装函数的默认参数。
您可以使用inspect.getargspec
函数获取函数签名中有关默认参数值的信息。您只需要使用参数名称正确匹配它们:
def document_call(fn):
argspec = inspect.getargspec(fn)
defaultArguments = list(reversed(zip(reversed(argspec.args), reversed(argspec.defaults))))
def wrapper(*args, **kwargs):
all_kwargs = kwargs.copy()
for arg, value in defaultArguments:
if arg not in kwargs:
all_kwargs[arg] = value
print 'function %s called with positional args %s and keyword args %s' % (fn.__name__, args, all_kwargs)
# still make the call using kwargs, to let the function handle its default values
return fn(*args, **kwargs)
return wrapper
请注意,您现在仍然可以改进这一点,因为您现在分别处理位置和命名参数。例如,在square
函数中,您还可以通过在trial
之后将其作为位置参数传递来设置n
。这将使其不会出现在kwargs
中。所以你必须将位置参数与你的kwargs相匹配才能获得完整的信息。您可以从argspec
获取有关职位的所有信息。
答案 3 :(得分:0)
以下是修改为与python3配合使用的代码
import inspect
import decorator
@decorator.decorator
def log_call(fn,*args, **kwargs):
sign = inspect.signature(fn)
arg_names = list(sign.parameters.keys())
passed = {k:v for k,v in zip(arg_names[:len(args)], args)}
passed.update({k:v for k,v in kwargs.items()})
params_str = ", ".join([f"{k}={passed.get(k, '??')}" for k in arg_names])
print (f"{fn.__name__}({params_str})")
return fn(*args, **kwargs)
请注意,我正在使用附加的库“装饰器”,因为它保留了函数签名。
答案 4 :(得分:0)
在python 3.6中,我使用了inspect.getfullargspec:
def document_call(func):
@wraps(func)
def decorator(*args, **kwargs):
fullargspec = getfullargspec(func)
default_kwargs = fullargspec.kwonlydefaults
print('Default kwargs', default_kwargs)
print('Passed kwargs', kwargs)
return func(*args, **kwargs)
return decorator
在定义装饰功能以使其工作时,请注意使用*
分隔符
@document_call
def square(n, *, trial=True, output=False):
# kwargs are a bit of nonsense to test function
if not output:
print 'no output'
if trial:
print n*n