9.11 装饰器为被包装函数增加�数¶

问题¶

你想在装饰器中给被包装函数增加�外的�数,但是�能影�这个函数现有的调用规则。

解决方案¶

�以使用关键字�数�给被包装函数增加�外�数。考虑下�的装饰器:

from functools import wraps

def optional_debug(func):
    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print('Calling', func.__name__)
        return func(*args, **kwargs)

    return wrapper
>>> @optional_debug
... def spam(a,b,c):
...     print(a,b,c)
...
>>> spam(1,2,3)
1 2 3
>>> spam(1,2,3, debug=True)
Calling spam
1 2 3
>>>

讨论¶

通过装饰器�给被包装函数增加�数的�法并�常�。 尽管如此,有时候它�以��一些��代�。例如,如果你有下�这样的代�:

def a(x, debug=False):
    if debug:
        print('Calling a')

def b(x, y, z, debug=False):
    if debug:
        print('Calling b')

def c(x, y, debug=False):
    if debug:
        print('Calling c')

那么你�以将其�构�这样:

from functools import wraps
import inspect

def optional_debug(func):
    if 'debug' in inspect.getargspec(func).args:
        raise TypeError('debug argument already defined')

    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print('Calling', func.__name__)
        return func(*args, **kwargs)
    return wrapper

@optional_debug
def a(x):
    pass

@optional_debug
def b(x, y, z):
    pass

@optional_debug
def c(x, y):
    pass

这�实现方案之所以行得通,在于强制关键字�数很容易被添加到接� *args 和 **kwargs �数的函数中。 通过使用强制关键字�数,它被作为一个特殊情况被挑选出�, 并且接下�仅仅使用剩余的�置和关键字�数去调用这个函数时,这个特殊�数会被排除在外。 也就是说,它并�会被纳入到 **kwargs 中去。

还有一个难点就是如何去处�被添加的�数与被包装函数�数直接的�字冲�。 例如,如果装饰器 @optional_debug 作用在一个已�拥有一个 debug �数的函数上时会有问题。 这里我们增加了一步�字检查。

上�的方案还�以更完美一点,因为精明的程�员应该�现了被包装函数的函数签�其实是错误的。例如:

>>> @optional_debug
... def add(x,y):
...     return x+y
...
>>> import inspect
>>> print(inspect.signature(add))
(x, y)
>>>

通过如下的修改,�以解决这个问题:

from functools import wraps
import inspect

def optional_debug(func):
    if 'debug' in inspect.getargspec(func).args:
        raise TypeError('debug argument already defined')

    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print('Calling', func.__name__)
        return func(*args, **kwargs)

    sig = inspect.signature(func)
    parms = list(sig.parameters.values())
    parms.append(inspect.Parameter('debug',
                inspect.Parameter.KEYWORD_ONLY,
                default=False))
    wrapper.__signature__ = sig.replace(parameters=parms)
    return wrapper

通过这样的修改,包装�的函数签�就能正确的显示 debug �数的存在了。例如:

>>> @optional_debug
... def add(x,y):
...     return x+y
...
>>> print(inspect.signature(add))
(x, y, *, debug=False)
>>> add(2,3)
5
>>>

�考9.16�节获�更多关于函数签�的信�。