30.3.8. functools.wraps定义函数修饰器

Python中有一种特殊的写法,可以用修饰器(decorator)来封装某个函数,从而让程序在执行这个函数之前与执行完这个函数之后,分别运行某些代码。

这意味着,调用者传给函数的参数值、函数返回给调用者的值,以及函数抛出的异常,都可以由修饰器访问并修改。

这是个很有用的机制,能够确保用户以正确的方式使用函数,也能够用来调试程序或实现函数注册功能,此外还有许多用途。

from functools import wraps
import pickle

def trace(func):
    # @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"{func.__name__}({args},{kwargs})"
              f"-> {result}")
        return result

    return wrapper


@trace
def fibonacci(n):
    """
    Args:
        n:
    Returns: the n-th Fibonacci number
    """
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))


fibonacci(4)
print(fibonacci)
print(pickle.dumps(fibonacci))

"""
Traceback (most recent call last):
AttributeError: Can't pickle local object 'trace.<locals>.wrapper'

<function trace.<locals>.wrapper at 0x0000024EF5613BF8>
"""

这种现象解释起来并不困难。trace函数返回的,是它里面定义的wrapper函数,所以,当我们把这个返回值赋给fibonacci之后,fibonacci这个名称所表示的自然就是wrapper了。

from functools import wraps
import pickle

def trace(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f"{func.__name__}({args},{kwargs})"
              f"-> {result}")
        return result

    return wrapper


@trace
def fibonacci(n):
    """
    Args:
        n:
    Returns: the n-th Fibonacci number
    """
    if n in (0, 1):
        return n
    return (fibonacci(n - 2) + fibonacci(n - 1))


fibonacci(4)
print(fibonacci)
print(pickle.dumps(fibonacci))
"""
<function trace.<locals>.wrapper at 0x0000024EF5613BF8>
b'\x80\x03c__main__\nfibonacci\nq\x00.'
"""

对象序列化器,现在也正常了。

除了这里讲到的几个方面之外,Python函数还有很多标准属性(例如___name__、__module__、__annotations__)也应该在受到封装时得以保留,这样才能让相关的接口正常运作。wraps可以帮助保留这些属性,使程序表现出正确的行为。

要点:

  • 修饰器是Python中的一种写法,能够把一个函数封装在另一个函数里面,这样程序在执行原函数之前与执行完毕之后,就有机会执行其他一些逻辑了。

  • 修饰器可能会让那些利用introspection机制运作的工具(例如调试器)产生奇怪的行为。

  • Python内置的functools模块里有个叫作wraps的修饰器,可以帮助我们正确定义自己的修饰器,从而避开相关的问题。