用于显示已传递和默认KWARG的python装饰器

2024-05-29 08:08:48 发布

您现在位置:Python中文网/ 问答频道 /正文

我是python和decorators的新手,在编写一个decorator时遇到了麻烦,它不仅报告传递的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

“问题”在于,此装饰器报告在对square的调用中传递的参数,但不报告square定义中定义的默认kwargs。报告KWARG的唯一方法是将KWARG从默认值更改为方形调用

关于如何在方形定义中报告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

那是一次很好的学习经历。看到同一个问题有三种不同的解决方案是很好的。感谢回答者


Tags: inoutputdef报告withargsfunctioncall
3条回答

由于decorator函数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获取有关位置的所有信息

您必须反思您包装的函数,才能读取默认值。您可以使用^{} 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}

从Python 3.5开始,您可以使用^{}用默认值填充缺少的参数:

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

相关问题 更多 >

    热门问题