Python 装饰器:一篇就懂
什么是装饰器?
装饰器,英文叫 Decorator,顾名思义就是"装饰"东西的工具。
它的作用是:在不修改原函数代码的前提下,给函数额外加点功能。
比如你想给一个函数加个计时、加个日志、加个权限校验,但又不想改动函数本身的代码——装饰器就是干这个的。
两个前置概念
在学装饰器之前,先了解两个 Python 的特性。
1. 函数是一等对象
在 Python 里,函数和普通变量一样,可以:
- 赋值给变量
- 作为参数传给另一个函数
- 在函数内部定义
def hello():
return "你好"
# 函数可以赋值给变量
func = hello
print(func()) # 输出:你好
2. 嵌套函数
函数内部可以定义函数:
def outer():
def inner():
return "我是内部函数"
return inner()
print(outer()) # 输出:我是内部函数
这两个特性是装饰器的基础。
从需求出发
假设你写了一个函数:
import time
def calculate():
"""计算 1+2+3+...+1000000"""
total = 0
for i in range(1000000):
total += i
print(f"结果是 {total}")
现在你想测量这个函数运行花了多长时间。
方法一:直接改函数(不推荐)
def calculate():
start = time.time()
total = 0
for i in range(1000000):
total += i
print(f"结果是 {total}")
end = time.time()
print(f"耗时 {end - start:.4f} 秒")
问题:如果你有很多函数都想计时,每个都加同样的计时代码?太麻烦了。
方法二:用一个包装函数
import time
def timer(func):
"""计时包装器"""
def wrapper():
start = time.time()
func() # 调用原函数
end = time.time()
print(f"耗时 {end - start:.4f} 秒")
return wrapper
def calculate():
total = 0
for i in range(1000000):
total += i
print(f"结果是 {total}")
# 用 wrapper 包装 calculate
calculate = timer(calculate)
calculate() # 输出结果 + 耗时
这个 timer 函数就是个装饰器——它"装饰"了 calculate,加了计时功能。
方法三:用 @ 语法糖(推荐)
上面的 calculate = timer(calculate) 可以简写为:
import time
def timer(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"耗时 {end - start:.4f} 秒")
return wrapper
@timer
def calculate():
total = 0
for i in range(1000000):
total += i
print(f"结果是 {total}")
calculate() # 调用时自动包装
@timer 就是 calculate = timer(calculate) 的简写。
装饰器的标准模板
绝大多数装饰器都长这样:
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数的名字和文档
def wrapper(*args, **kwargs):
# 调用原函数之前做的事
print("调用前")
result = func(*args, **kwargs) # 调用原函数
# 调用原函数之后做的事
print("调用后")
return result
return wrapper
关键点:
*args, **kwargs:接收任意参数,这样装饰的函数不管有什么参数都能正常工作functools.wraps:保留原函数的__name__、__doc__等信息wrapper要return原函数的返回值
实际例子
例子 1:计时器
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时 {end - start:.4f} 秒")
return result
return wrapper
@timer
def add():
total = sum(range(1000000))
print(f"结果是 {total}")
add()
例子 2:日志记录
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] 调用函数 {func.__name__},参数 args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} 返回 {result}")
return result
return wrapper
@log
def greet(name, greeting="你好"):
return f"{greeting}, {name}!"
greet("小明", greeting="早上好")
例子 3:带参数的装饰器
有时候装饰器本身也需要参数,比如你想控制日志的级别:
import functools
def repeat(times):
"""带参数的装饰器——外层再包一层"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
print("你好!")
say_hello() # 输出三遍"你好!"
三层结构:
- 最外层
repeat(times):接收装饰器参数 - 中间层
decorator(func):接收被装饰的函数 - 最内层
wrapper:实际包装逻辑
例子 4:多个装饰器叠加
@log
@timer
def add(a, b):
return a + b
# 等价于:add = log(timer(add))
# 叠加(应用)顺序:从下到上,先 timer 再 log
# 调用执行顺序:从外到内,先 log → timer → add
多个装饰器叠加时,应用顺序从下到上,调用执行顺序从外到内。
总结
| 要点 | 说明 |
|---|---|
| 本质 | 一个接收函数、返回新函数的普通函数 |
@ 语法 | @decorator 等价于 func = decorator(func) |
wrapper | 接收 *args, **kwargs,调用原函数并返回结果 |
@functools.wraps | 保留原函数的名字、文档等信息 |
| 多个装饰器 | 从下到上叠加,从里到外执行 |
装饰器看起来绕,但核心思想就一句话:用一个函数去包装另一个函数,在不改原函数代码的情况下加功能。