跳转到主要内容
模块 9 · 第 4 课(共 4)课程中的36/36~14 min
模块课程(4/4)

装饰器:修饰函数的函数

装饰器是一个函数,它接受另一个函数并返回一个 它的“装饰”版本。这是添加行为的有效方法 (日志记录、计时、缓存、重试、验证),无需触及主体 原来的函数。

函数作为值

在 Python 中,函数是对象:您可以传递它们、返回它们、分配它们。

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

一个最小的装饰器

Python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"chiamo {func.__name__}")
        risultato = func(*args, **kwargs)
        print(f"ho finito {func.__name__}")
        return risultato
    return wrapper

def somma(a, b):
    return a + b

somma = log(somma)   # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5

@decorator 语法

使用 @ 可以更优雅地编写赋值 somma = log(somma):

Python
@log
def somma(a, b):
    return a + b

这是完全相同的事情。上面的 @log 定义相当于: “一旦定义了此函数,请将其替换为 log(function)”。

*args, **kwargs:接受任何签名

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args 收集位置参数,**kwargs 关键字参数。他们让 您编写适用于任何函数的包装器。

functools.wraps:保留元数据

如果没有wraps,修饰函数就会失去__name__、__doc__,提示:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

示例:@timeit

Python
from functools import wraps
import time

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        risultato = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
        return risultato
    return wrapper

@timeit
def somma_grande():
    return sum(range(10**6))

somma_grande()

来自 stdlib 的装饰器

Python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

fib(100)  # istantaneo grazie alla cache
  • @lru_cache — 记住纯函数的结果。
  • @property — 将方法公开为属性(在 OOP 模块中看到)。
  • @staticmethod、@classmethod — 更改 self 的传递方式。

带参数的装饰器

装饰器也可以接受参数。要实现此目的,必须编写三层嵌套函数:最外层函数接收装饰器的参数,中间函数接收装饰函数,最内层函数 (wrapper) 接收调用期间传递给装饰函数的实际参数。

自己尝试一下

锻炼#python.m9.l4.e1
尝试:0加载中...

编写一个装饰器“double_result”,调用装饰函数并返回其结果 * 2. 将其应用于返回 x + 1 的函数“f(x)”。评估“f(10)”。

正在加载编辑器...
显示提示

包装器调用 func 并返回乘以 2 的结果。

3 次尝试后可用的解决方案

复习练习

锻炼#python.m9.l4.e2
尝试:0加载中...

使用 functools.lru_cache 加速递归 fib(n) 函数。计算 fib(30) 并分配给“r”。评估“r”。

正在加载编辑器...
显示提示

@lru_cache(maxsize=None) 高于 def。

3 次尝试后可用的解决方案

额外的挑战

锻炼#python.m9.l4.e3
尝试:0加载中...

Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.

正在加载编辑器...
显示提示

包装器调用 func(*args, **kwargs) 并将结果乘以 2。

3 次尝试后可用的解决方案