python decorator显示传递的AND默认kwargs

时间:2016-01-16 21:55:21

标签: python decorator default python-decorators kwargs

我是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!

5 个答案:

答案 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