Applying functools.wraps to nested wrappers

时间:2016-08-31 17:53:05

标签: python python-2.7 python-decorators functools

I have a base decorator that takes arguments but that also is built upon by other decorators. I can't seem to figure where to put the functools.wraps in order to preserve the full signature of the decorated function.

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')

The result of this code should be and is:

saysay hello argumentsarguments

However, running help on say_hello gives me:

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

Everything is preserved except the parameter names.

It seems like I just need to add another @wraps() somewhere, but where?

2 个答案:

答案 0 :(得分:0)

我试验了这个:

>>> 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(),但在哪里?

不,您不需要在代码中添加另一个wraps help,正如我上面所说的那样,通过检查您的对象。函数的参数与代码对象(__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?

答案 1 :(得分:0)

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