装饰器是什么
装饰器本质上是一个接收函数、返回函数的函数。它在不改动函数本体的前提下,给它"套上一层"——可以加日志、计时、权限、缓存……
最常见的用途:日志、计时、缓存、注册路由、注入参数。
第一步:理解函数是值
回顾一下基础——函数本身是个对象,可以赋给变量、传给别人:
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
读懂这段就读懂了装饰器:
log是个函数,接收另一个函数func- 它定义了一个
wrapper,里面在调用func前后做点别的事 - 返回
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保留元信息
下一步
下一篇讲带参数的装饰器、类装饰器、多层堆叠——把这套机制玩到极致。