【问题标题】:Applying functools.wraps to nested wrappers将 functools.wraps 应用于嵌套包装器
【发布时间】:2017-01-08 10:12:55
【问题描述】:

我有一个基础装饰器,它接受参数,但也由其他装饰器构建。我似乎无法确定将 functools.wraps 放在哪里以保留装饰函数的完整签名。

import inspect
from functools import wraps

# Base decorator
def _process_arguments(func, *indices):
    """ Apply the pre-processing function to each selected parameter """
    @wraps(func)
    def wrap(f):
        @wraps(f)
        def wrapped_f(*args):
            params = inspect.getargspec(f)[0]

            args_out = list()
            for ind, arg in enumerate(args):
                if ind in indices:
                    args_out.append(func(arg))
                else:
                    args_out.append(arg)

            return f(*args_out)
        return wrapped_f
    return wrap


# Function that will be used to process each parameter
def double(x):
    return x * 2

# Decorator called by end user
def double_selected(*args):
    return _process_arguments(double, *args)

# End-user's function
@double_selected(2, 0)
def say_hello(a1, a2, a3):
    """ doc string for say_hello """
    print('{} {} {}'.format(a1, a2, a3))

say_hello('say', 'hello', 'arguments')

这段代码的结果应该是

saysay hello argumentsarguments

但是,在 say_hello 上运行帮助会给我:

say_hello(*args, **kwargs)
    doc string for say_hello

除参数名称外,所有内容均被保留。

似乎我只需要在某处添加另一个@wraps(),但是在哪里?

【问题讨论】:

  • 你用的是python 2还是python 3?

标签: python python-2.7 python-decorators functools


【解决方案1】:

我对此进行了实验:

>>> from functools import wraps
>>> def x(): print(1)
...
>>> @wraps(x)
... def xyz(a,b,c): return x


>>> xyz.__name__
'x'
>>> help(xyz)
Help on function x in module __main__:

x(a, b, c)

AFAIK,这与wraps 本身无关,而是与help 有关的问题。事实上,因为help 检查您的对象以提供信息,包括__doc__ 和其他属性,这就是您得到此行为的原因,尽管您的包装函数具有不同的参数列表。虽然,wraps 不会自动更新(参数列表)它真正更新的是这个元组和__dict__,这在技术上是对象命名空间:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)

如果您不确定wraps 的工作原理,阅读标准库中的源代码可能会有所帮助:functools.py

似乎我只需要在某处添加另一个@wraps(),但是在哪里?

不,您不需要在代码中添加另一个 wrapshelp 正如我在上面所说的那样通过检查您的对象来工作。函数的参数与代码对象 (__code__) 相关联,因为您的函数的参数存储/表示在该对象中,wraps 无法将包装器的参数更新为类似于包装的函数(继续上面的示例):

>>> xyz.__code__.co_varnames

>>> xyz.__code__.co_varnames = x.__code__.co_varnames
AttributeError: readonly attribute

如果help 显示函数xyz 有这个参数列表() 而不是(a, b, c),那么这显然是错误的!同样适用于wraps,将包装器的参数列表更改为包装器,会很麻烦!所以这根本不应该是一个问题。

>>> @wraps(x, ("__code__",))
... def xyz(a,b,c): pass
...

>>> help(xyz)
Help on function xyz in module __main__:

xyz()

但是xyz()返回x()

>>> xyz()
1

有关其他参考资料,请查看此问题或 Python 文档

What does functools.wraps do?

【讨论】:

    【解决方案2】:

    direprobs 是正确的,没有多少 functools 包装可以让我到达那里。 bravosierra99 向我指出了一些相关的例子。但是,我找不到一个在嵌套装饰器上保留签名的示例,其中外部装饰器接受参数。

    带有参数的装饰器上的comments on Bruce Eckel's post 给了我最大的暗示来实现我想要的结果。

    关键在于从我的 _process_arguments 函数中删除中间函数并将其参数放入下一个嵌套函数中。现在对我来说有点道理......但它有效:

    import inspect
    from decorator import decorator
    
    # Base decorator
    def _process_arguments(func, *indices):
        """ Apply the pre-processing function to each selected parameter """
        @decorator
        def wrapped_f(f, *args):
            params = inspect.getargspec(f)[0]
    
            args_out = list()
            for ind, arg in enumerate(args):
                if ind in indices:
                    args_out.append(func(arg))
                else:
                    args_out.append(arg)
    
            return f(*args_out)
        return wrapped_f
    
    
    # Function that will be used to process each parameter
    def double(x):
        return x * 2
    
    # Decorator called by end user
    def double_selected(*args):
        return _process_arguments(double, *args)
    
    # End-user's function
    @double_selected(2, 0)
    def say_hello(a1, a2,a3):
        """ doc string for say_hello """
        print('{} {} {}'.format(a1, a2, a3))
    
    say_hello('say', 'hello', 'arguments')
    print(help(say_hello))
    

    结果:

    saysay hello argumentsarguments
    Help on function say_hello in module __main__:
    
    say_hello(a1, a2, a3)
        doc string for say_hello
    

    【讨论】:

      猜你喜欢
      • 2015-04-21
      • 1970-01-01
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多