【问题标题】:python decorator to display passed AND default kwargspython装饰器显示传递的和默认的kwargs
【发布时间】:2016-04-22 07:35:30
【问题描述】:

我是 python 和装饰器的新手,在编写一个装饰器时遇到了困难,它不仅报告传递的 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 调用中传递的 args,但没有报告 square 定义中定义的默认 kwargs。报告 kwargs 的唯一方法是如果它们从默认值更改,即传递给 square 调用。

关于如何获得方形定义中的 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

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

【问题讨论】:

  • @Martijn,谢谢;我已经阅读了您答案的前两行,并且会离开,了解有关检查的所有信息,然后看看我想出了什么。然后会回来看看如何真正做到这一点;-)

标签: python decorator default python-decorators keyword-argument


【解决方案1】:

从 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】:

    在 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
    
    

    【讨论】:

      【解决方案3】:

      这是修改后的代码以使用 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】:

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

        【讨论】:

          【解决方案5】:

          由于装饰器函数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 函数中,您还可以通过在n 之后将其作为位置参数传递来设置trial。这将使它不会出现在kwargs 中。因此,您必须将位置参数与您的 kwargs 进行匹配才能获得完整的信息。您可以从argspec获取有关职位的所有信息。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-08-28
            • 2015-10-22
            • 2021-08-19
            • 1970-01-01
            • 2019-02-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多