装饰器是什么

装饰器本质上是一个接收函数、返回函数的函数。它在不改动函数本体的前提下,给它"套上一层"——可以加日志、计时、权限、缓存……

最常见的用途:日志、计时、缓存、注册路由、注入参数。

第一步:理解函数是值

回顾一下基础——函数本身是个对象,可以赋给变量、传给别人:

def shout(text):
    return text.upper()

f = shout
print(f("hi"))   # HI

既然能赋值,自然也能当参数传当返回值传

第二步:手写一个装饰器

加一层"打印调用信息"的壳:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}, 参数 {args} {kwargs}")
        result = func(*args, **kwargs)
        print(f"返回 {result}")
        return result
    return wrapper

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

add = log(add)        # 给 add 套一层
add(3, 4)
# 调用 add, 参数 (3, 4) {}
# 返回 7

读懂这段就读懂了装饰器:

  1. log 是个函数,接收另一个函数 func
  2. 它定义了一个 wrapper,里面在调用 func 前后做点别的事
  3. 返回 wrapper——从此 add 这个名字指向的是 wrapper,不再是原函数

第三步:@ 语法糖

add = log(add) 这一行可以写成更优雅的形式:

@log
def add(a, b):
    return a + b

@log 完全等价于 add = log(add)——只是写在 def 上面更显眼。

完整例子:计时装饰器

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        cost = time.time() - start
        print(f"[{func.__name__}] 耗时 {cost*1000:.2f} ms")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.5)
    return a + b

slow_add(3, 4)
# [slow_add] 耗时 503.21 ms

为什么这么常见

Python 的 Web 框架(Flask、FastAPI)大量用装饰器注册路由:

@app.get("/users")
def list_users():
    ...

测试框架用它标记 fixture、缓存库 functools.lru_cache 用它做记忆化——装饰器是 Python 横切关注点(cross-cutting concern)的优雅表达

一个细节:保留原函数信息

经过装饰,add.__name__ 会变成 wrapper——丢失了原函数的元信息。用 functools.wraps 修复:

from functools import wraps

def log(func):
    @wraps(func)        # 保留 func 的 __name__ / __doc__
    def wrapper(*args, **kwargs):
        ...
    return wrapper

写装饰器时永远加上 @wraps,这是规矩。

小结

  • 装饰器 = 接收函数、返回新函数的函数
  • @deco 等价于 f = deco(f)
  • 内部用 *args, **kwargs 转发参数,保持通用
  • 别忘了 @functools.wraps 保留元信息

下一步

下一篇讲带参数的装饰器、类装饰器、多层堆叠——把这套机制玩到极致。