目录
引言:为什么装饰器是 Python 开发者的 "效率神器"?
class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
引言:为什么装饰器是 Python 开发者的 "效率神器"?
在 Python 开发中,你是否遇到过这样的场景:
- 想给多个函数添加日志记录功能,却不想在每个函数里重复写日志代码;
- 想给接口添加权限验证,却不想修改接口的核心逻辑;
- 想统计函数的执行时间,却不想侵入函数内部实现。
这时候,装饰器就能派上大用场!它作为 Python 的核心语法糖,本质是 "函数包装器",能在不修改原函数代码、不改变函数调用方式的前提下,为函数添加额外功能。这种 "无侵入式增强" 的特性,完美契合了AOP(面向切面编程) 思想,让代码更简洁、更易维护。

装饰器的应用场景远不止这些:缓存控制、异常捕获、参数校验、事务管理等场景都能看到它的身影。无论是开发基础工具还是大型框架(如 Django、Flask 中的@route、@login_required),装饰器都是提升开发效率的关键工具。
本文将从 "零基础理解" 到 "高级实战",用通俗的语言 + 可运行的示例,系统拆解 Python 装饰器的核心原理与应用技巧。全程无晦涩概念堆砌,让你从 "会用" 到 "吃透",真正掌握这个 Python 开发者必备的核心技能。
一、基础铺垫:理解装饰器的 "前世今生"

在学习装饰器之前,必须先掌握 Python 的一个核心概念:函数是一等公民。这是装饰器实现的基础,也是理解其原理的关键。
1.1 函数是一等公民:装饰器的基石
Python 中的函数具备以下特性,这让它能像普通变量一样被操作:
- 函数可以赋值给变量;
- 函数可以作为参数传递给其他函数;
- 函数可以作为其他函数的返回值;
- 函数可以在函数内部定义(嵌套函数)。
示例:函数的 "一等公民" 特性
# 1. 函数可以赋值给变量
def greet(name):
return f"Hello, {name}!"
hello = greet # 将函数赋值给变量
print(hello("Python")) # 输出:Hello, Python!
# 2. 函数可以作为参数传递
def apply_func(func, arg):
return func(arg)
result = apply_func(greet, "Decorator")
print(result) # 输出:Hello, Decorator!
# 3. 函数可以作为返回值
def create_greeter():
def inner_greet(name):
return f"Hi, {name}!"
return inner_greet # 返回嵌套函数
greeter = create_greeter()
print(greeter("World")) # 输出:Hi, World!
# 4. 函数内部可以定义函数(嵌套函数)
def outer():
def inner():
return "I'm inner function!"
return inner()
print(outer()) # 输出:I'm inner function!
正是这些特性,让 Python 能够实现 "用函数包装函数" 的装饰器模式。
1.2 装饰器的本质:一个 "函数包装器"
装饰器本质上是一个接收函数作为参数,并返回新函数的函数。它的核心逻辑是:
- 接收一个待增强的函数(被装饰函数);
- 定义一个新函数,在新函数中调用被装饰函数,并添加额外功能;
- 返回这个新函数,替代原函数。
示例:最简单的装饰器
# 定义装饰器(接收函数作为参数)
def my_decorator(func):
# 定义嵌套函数(包装函数)
def wrapper():
print("函数执行前:添加额外功能")
func() # 调用被装饰函数
print("函数执行后:添加额外功能")
# 返回包装函数
return wrapper
# 定义被装饰函数
def say_hello():
print("Hello, Decorator!")
# 使用装饰器:用包装函数替代原函数
say_hello = my_decorator(say_hello)
# 调用函数(实际调用的是wrapper)
say_hello()
运行结果:
函数执行前:添加额外功能
Hello, Decorator!
函数执行后:添加额外功能
可以看到,我们没有修改say_hello函数的任何代码,却为它添加了 "执行前 / 后打印日志" 的功能。这就是装饰器的核心价值:无侵入式增强函数功能。
1.3 语法糖:@符号的便捷用法
上面的示例中,say_hello = my_decorator(say_hello)的写法略显繁琐。Python 提供了@符号作为装饰器的语法糖,让代码更简洁:
示例:使用@符号简化装饰器调用
def my_decorator(func):
def wrapper():
print("函数执行前:添加额外功能")
func()
print("函数执行后:添加额外功能")
return wrapper
# 使用@符号应用装饰器(等价于 say_hello = my_decorator(say_hello))
@my_decorator
def say_hello():
print("Hello, Decorator!")
# 直接调用函数即可
say_hello()
运行结果与之前完全一致。@my_decorator就像给say_hello函数 "穿上了一件新衣服",这件衣服就是装饰器提供的额外功能。
二、装饰器进阶:处理复杂场景

基础装饰器只能处理无参数、无返回值的简单函数。实际开发中,函数往往有参数、有返回值,甚至装饰器本身也需要参数。本节将拆解这些复杂场景的实现方式。
2.1 处理带参数的被装饰函数
如果被装饰函数有参数,直接调用func()会报错。解决思路是:让包装函数wrapper接收任意参数,并传递给func。
示例:支持任意参数的装饰器
def log_decorator(func):
# 使用*args接收位置参数,**kwargs接收关键字参数
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
print(f"位置参数:{args}")
print(f"关键字参数:{kwargs}")
# 将参数传递给被装饰函数
result = func(*args, **kwargs)
print(f"函数返回值:{result}")
return result # 返回被装饰函数的结果
return wrapper
# 带参数的被装饰函数
@log_decorator
def add(a, b):
return a + b
@log_decorator
def greet(name, message="Hello"):
return f"{message}, {name}!"
# 调用函数
add(10, 20)
print("-" * 20)
greet("Python", message="Hi")
运行结果:
调用函数:add
位置参数:(10, 20)
关键字参数:{}
函数返回值:30
--------------------
调用函数:greet
位置参数:('Python',)
关键字参数:{'message': 'Hi'}
函数返回值:Hi, Python!
通过*args和**kwargs,装饰器可以适配任意参数的函数,通用性大大提升。
2.2 带参数的装饰器
有时装饰器本身需要参数来控制其行为(比如日志装饰器需要指定日志级别)。这种情况下,需要在装饰器外层再套一层 "参数接收函数"。
示例:带参数的日志装饰器
# 外层函数:接收装饰器参数
def log_decorator(level="info"):
# 中层函数:接收被装饰函数
def decorator(func):
# 内层函数:包装逻辑
def wrapper(*args, **kwargs):
print(f"[{level.upper()}] 调用函数:{func.__name__}")
result = func(*args, **kwargs)
print(f"[{level.upper()}] 函数执行完成")
return result
return wrapper
return decorator
# 使用装饰器并传递参数
@log_decorator(level="debug")
def add(a, b):
return a + b
@log_decorator(level="warning")
def delete(id):
print(f"删除ID为{id}的数据")
# 调用函数
add(10, 20)
print("-" * 20)
delete(1001)
运行结果:
[DEBUG] 调用函数:add
[DEBUG] 函数执行完成
--------------------
[WARNING] 调用函数:delete
删除ID为1001的数据
[WARNING] 函数执行完成
带参数的装饰器执行流程:
- 执行
log_decorator(level="debug"),返回decorator函数; - 执行
decorator(add),返回wrapper函数; add函数被替换为wrapper,调用add(10,20)实际执行wrapper(10,20)。
2.3 保留被装饰函数的元信息
使用装饰器后,被装饰函数的元信息(如函数名、文档字符串)会被包装函数wrapper覆盖,这可能导致一些问题(比如调试时无法获取正确的函数名)。
问题示例:元信息被覆盖
def my_decorator(func):
def wrapper(*args, **kwargs):
"""这是包装函数"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
"""这是加法函数,返回a+b的结果"""
return a + b
# 查看函数名和文档字符串
print(add.__name__) # 输出:wrapper(预期是add)
print(add.__doc__) # 输出:这是包装函数(预期是加法函数的文档)
解决方案:使用functools.wraps
functools.wraps是 Python 提供的工具,用于将被装饰函数的元信息复制到包装函数中:
import functools
def my_decorator(func):
# 使用functools.wraps装饰wrapper,保留func的元信息
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""这是包装函数"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
"""这是加法函数,返回a+b的结果"""
return a + b
# 再次查看元信息
print(add.__name__) # 输出:add(正确)
print(add.__doc__) # 输出:这是加法函数,返回a+b的结果(正确)
最佳实践:所有装饰器的包装函数都应使用@functools.wraps(func),避免元信息丢失。
2.4 装饰器的嵌套使用
一个函数可以同时应用多个装饰器,执行顺序遵循 "就近原则":靠近函数的装饰器先执行,远离函数的装饰器后执行。
示例:装饰器嵌套
import functools
# 装饰器1:日志记录
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[日志] 调用函数:{func.__name__}")
result = func(*args, **kwargs)
print(f"[日志] 函数执行完成")
return result
return wrapper
# 装饰器2:性能计时
def time_decorator(func):
import time
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"[计时] 函数执行耗时:{end_time - start_time:.4f}秒")
return result
return wrapper
# 嵌套应用装饰器:先执行log_decorator,再执行time_decorator
@time_decorator
@log_decorator
def slow_function():
"""模拟耗时操作"""
import time
time.sleep(1)
print("耗时操作执行完成")
# 调用函数
slow_function()
运行结果:
[日志] 调用函数:slow_function
耗时操作执行完成
[日志] 函数执行完成
[计时] 函数执行耗时:1.0012秒
执行顺序解析:
@time_decorator和@log_decorator嵌套,等价于slow_function = time_decorator(log_decorator(slow_function));- 调用
slow_function()时,先执行time_decorator的wrapper; - 在
time_decorator的wrapper中调用func,实际是log_decorator的wrapper; - 在
log_decorator的wrapper中调用原始的slow_function; - 执行完成后,依次返回并执行后续逻辑。
三、类装饰器:另一种实现方式

除了函数装饰器,Python 还支持类装饰器。类装饰器通过重写__call__方法实现,当被装饰函数被调用时,会触发__call__方法的执行。
3.1 基础类装饰器
类装饰器的核心是:将被装饰函数作为参数传入类的构造函数,并重写__call__方法实现包装逻辑。
示例:基础类装饰器
import functools
class LogDecorator:
# 构造函数:接收被装饰函数
def __init__(self, func):
self.func = func
# 保留被装饰函数的元信息
functools.update_wrapper(self, func)
# 重写__call__方法:实现包装逻辑
def __call__(self, *args, **kwargs):
print(f"[类装饰器] 调用函数:{self.func.__name__}")
result = self.func(*args, **kwargs)
print(f"[类装饰器] 函数执行完成")
return result
# 使用类装饰器
@LogDecorator
def add(a, b):
"""这是加法函数"""
return a + b
# 调用函数(触发__call__方法)
print(add(10, 20))
print(add.__name__) # 输出:add(元信息保留)
运行结果:
[类装饰器] 调用函数:add
[类装饰器] 函数执行完成
30
add
3.2 带参数的类装饰器
如果类装饰器需要接收参数,需要在构造函数外层再套一层逻辑(类似函数装饰器的参数传递):
import functools
class LogDecorator:
# 外层函数:接收装饰器参数
def __init__(self, level="info"):
self.level = level
# 重写__call__方法:接收被装饰函数
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[{self.level.upper()}] 调用函数:{func.__name__}")
result = func(*args, **kwargs)
print(f"[{self.level.upper()}] 函数执行完成")
return result
return wrapper
# 使用带参数的类装饰器
@LogDecorator(level="debug")
def multiply(a, b):
return a * b
# 调用函数
print(multiply(5, 6))
运行结果:
[DEBUG] 调用函数:multiply
[DEBUG] 函数执行完成
30
3.3 函数装饰器 vs 类装饰器:如何选择?
| 对比维度 | 函数装饰器 | 类装饰器 |
|---|---|---|
| 实现复杂度 | 简单(函数嵌套) | 中等(类 +__call__方法) |
| 状态保持 | 需借助闭包或全局变量 | 天然支持(实例属性) |
| 灵活性 | 高(易修改包装逻辑) | 中等(需遵循类的设计规范) |
| 可读性 | 适合简单逻辑(代码简洁) | 适合复杂逻辑(结构清晰) |
| 适用场景 | 简单功能增强(日志、计时) | 复杂功能(状态管理、缓存) |
选择建议:
- 简单场景(如日志、计时)优先用函数装饰器,代码更简洁;
- 复杂场景(如需要维护状态的缓存装饰器)用类装饰器,结构更清晰。
四、实战场景:装饰器的 10 个经典应用

装饰器的价值在于解决实际问题。本节将结合企业开发中的高频场景,提供 10 个可直接复用的装饰器实战案例。
4.1 场景 1:日志记录装饰器
为函数添加详细的日志记录,包括调用参数、返回值、执行时间,便于调试和问题排查。
import functools
import logging
import time
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 记录调用信息
logging.info(f"函数 {func.__name__} 开始执行")
logging.info(f"位置参数:{args}")
logging.info(f"关键字参数:{kwargs}")
start_time = time.time()
try:
# 执行函数并获取返回值
result = func(*args, **kwargs)
logging.info(f"函数 {func.__name__} 执行成功,返回值:{result}")
return result
except Exception as e:
# 记录异常信息
logging.error(f"函数 {func.__name__} 执行失败,异常:{str(e)}", exc_info=True)
raise # 重新抛出异常,不影响原函数的异常处理
finally:
# 记录执行时间
end_time = time.time()
logging.info(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f}秒")
return wrapper
# 测试
@log_decorator
def divide(a, b):
return a / b
divide(10, 2)
divide(10, 0) # 触发异常
运行结果:
2024-05-20 10:00:00,000 - INFO - 函数 divide 开始执行
2024-05-20 10:00:00,000 - INFO - 位置参数:(10, 2)
2024-05-20 10:00:00,000 - INFO - 关键字参数:{}
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行成功,返回值:5.0
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行耗时:0.0000秒
2024-05-20 10:00:00,000 - INFO - 函数 divide 开始执行
2024-05-20 10:00:00,000 - INFO - 位置参数:(10, 0)
2024-05-20 10:00:00,000 - INFO - 关键字参数:{}
2024-05-20 10:00:00,000 - ERROR - 函数 divide 执行失败,异常:division by zero
Traceback (most recent call last):
File "...", line 22, in wrapper
result = func(*args, **kwargs)
File "...", line 36, in divide
return a / b
ZeroDivisionError: division by zero
2024-05-20 10:00:00,000 - INFO - 函数 divide 执行耗时:0.0000秒
4.2 场景 2:性能计时装饰器
统计函数的执行时间,用于性能瓶颈分析。
import functools
import time
import logging
logging.basicConfig(level=logging.INFO)
def time_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter() # 高精度计时
result = func(*args, **kwargs)
end_time = time.perf_counter()
elapsed = end_time - start_time
# 按执行时间分级记录日志
if elapsed < 0.1:
logging.info(f"函数 {func.__name__} 执行耗时:{elapsed:.6f}秒(快速)")
elif elapsed < 1:
logging.info(f"函数 {func.__name__} 执行耗时:{elapsed:.4f}秒(正常)")
else:
logging.warning(f"函数 {func.__name__} 执行耗时:{elapsed:.2f}秒(缓慢)")
return result
return wrapper
# 测试
@time_decorator
def fast_function():
time.sleep(0.05)
@time_decorator
def slow_function():
time.sleep(1.5)
fast_function()
slow_function()
运行结果:
INFO:root:函数 fast_function 执行耗时:0.050123秒(快速)
WARNING:root:函数 slow_function 执行耗时:1.5023秒(缓慢)
4.3 场景 3:缓存装饰器
缓存函数的执行结果,避免重复计算(适用于计算密集型、参数重复的函数)。
import functools
import time
def cache_decorator(func):
# 用字典缓存结果:key为参数元组,value为返回值
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 将参数转换为可哈希的元组作为key(kwargs按排序转换为元组)
key = args + tuple(sorted(kwargs.items()))
if key in cache:
print(f"[缓存命中] 函数 {func.__name__} 直接返回缓存结果")
return cache[key]
# 缓存未命中,执行函数并缓存结果
result = func(*args, **kwargs)
cache[key] = result
print(f"[缓存未命中] 函数 {func.__name__} 执行并缓存结果")
return result
return wrapper
# 测试:计算斐波那契数列(递归方式,存在大量重复计算)
@cache_decorator
def fibonacci(n):
if n <= 1:
return n
time.sleep(0.1) # 模拟计算耗时
return fibonacci(n-1) + fibonacci(n-2)
# 第一次调用:缓存未命中
print(fibonacci(5))
print("-" * 20)
# 第二次调用:缓存命中
print(fibonacci(5))
运行结果:
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
[缓存未命中] 函数 fibonacci 执行并缓存结果
5
--------------------
[缓存命中] 函数 fibonacci 直接返回缓存结果
5
进阶:Python 内置的functools.lru_cache是更强大的缓存装饰器,支持缓存大小限制、过期时间等功能:
import functools
import time
# maxsize:缓存最大数量,None表示无限制;typed:是否区分参数类型
@functools.lru_cache(maxsize=None, typed=True)
def fibonacci(n):
if n <= 1:
return n
time.sleep(0.1)
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(5))
print(fibonacci.cache_info()) # 查看缓存信息
4.4 场景 4:权限验证装饰器
在接口或函数执行前验证用户权限,适用于 Web 应用、后台系统等需要权限控制的场景。
import functools
# 模拟用户信息(实际开发中从数据库/会话获取)
current_user = {
"username": "admin",
"role": "admin" # 角色:admin/editor/guest
}
def permission_required(required_role):
"""权限验证装饰器:需要指定所需角色"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 验证当前用户角色
if current_user["role"] != required_role:
raise PermissionError(f"权限不足!需要 {required_role} 角色,当前角色:{current_user['role']}")
print(f"权限验证通过!当前用户:{current_user['username']}")
return func(*args, **kwargs)
return wrapper
return decorator
# 测试:不同角色的函数
@permission_required("admin")
def delete_all_data():
print("删除所有数据(仅管理员可执行)")
@permission_required("editor")
def edit_data(id):
print(f"编辑ID为{id}的数据(编辑者可执行)")
# 管理员角色测试
delete_all_data()
edit_data(1001)
# 切换用户角色为guest
current_user["role"] = "guest"
try:
delete_all_data()
except PermissionError as e:
print(e)
运行结果:
权限验证通过!当前用户:admin
删除所有数据(仅管理员可执行)
权限验证通过!当前用户:admin
编辑ID为1001的数据(编辑者可执行)
权限不足!需要 admin 角色,当前角色:guest
4.5 场景 5:参数校验装饰器
验证函数参数的类型、范围等合法性,避免无效参数导致的错误。
import functools
from typing import Dict, List, Type
def validate_params(param_rules: Dict[str, Type]):
"""参数校验装饰器:验证参数类型是否符合规则"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 结合函数签名获取参数名(处理位置参数)
import inspect
sig = inspect.signature(func)
params = list(sig.parameters.keys())
# 验证位置参数
for i, (param_name, param_type) in enumerate(param_rules.items()):
if i >= len(args):
break # 无该位置参数,跳过
arg_value = args[i]
if not isinstance(arg_value, param_type):
raise TypeError(f"参数 {param_name} 类型错误!预期 {param_type},实际 {type(arg_value)}")
# 验证关键字参数
for param_name, param_type in param_rules.items():
if param_name in kwargs:
arg_value = kwargs[param_name]
if not isinstance(arg_value, param_type):
raise TypeError(f"参数 {param_name} 类型错误!预期 {param_type},实际 {type(arg_value)}")
return func(*args, **kwargs)
return wrapper
return decorator
# 测试:参数类型校验
@validate_params({"a": int, "b": int})
def add(a, b):
return a + b
@validate_params({"name": str, "age": int})
def user_info(name, age):
return f"姓名:{name},年龄:{age}"
# 正确调用
print(add(10, 20))
print(user_info("Python", 30))
# 错误调用(参数类型不匹配)
try:
add(10, "20")
except TypeError as e:
print(e)
try:
user_info("Python", "30")
except TypeError as e:
print(e)
运行结果:
30
姓名:Python,年龄:30
参数 b 类型错误!预期 <class 'int'>,实际 <class 'str'>
参数 age 类型错误!预期 <class 'int'>,实际 <class 'str'>
4.6 场景 6:异常捕获装饰器
统一捕获函数执行过程中的异常,避免程序崩溃,并进行友好处理。
import functools
import logging
logging.basicConfig(level=logging.ERROR)
def exception_handler(default_return=None, log_exception=True):
"""异常捕获装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
# 记录异常日志
if log_exception:
logging.error(f"函数 {func.__name__} 执行异常:{str(e)}", exc_info=True)
# 返回默认值(避免程序崩溃)
return default_return
return wrapper
return decorator
# 测试:文件读取函数(可能抛出文件不存在异常)
@exception_handler(default_return="", log_exception=True)
def read_file(file_path):
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
# 读取不存在的文件
content = read_file("nonexistent.txt")
print(f"文件内容:{content}") # 输出:文件内容:(默认返回空字符串)
运行结果:
ERROR:root:函数 read_file 执行异常:[Errno 2] No such file or directory: 'nonexistent.txt'
Traceback (most recent call last):
File "...", line 14, in wrapper
return func(*args, **kwargs)
File "...", line 26, in read_file
with open(file_path, "r", encoding="utf-8") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'nonexistent.txt'
文件内容:
4.7 场景 7:重试装饰器
当函数执行失败(如网络请求超时、数据库连接失败)时,自动重试指定次数。
import functools
import time
import logging
logging.basicConfig(level=logging.INFO)
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
"""重试装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempts += 1
if attempts >= max_attempts:
logging.error(f"函数 {func.__name__} 重试 {max_attempts} 次失败,异常:{str(e)}")
raise # 重试次数耗尽,抛出异常
logging.warning(f"函数 {func.__name__} 执行失败,将在 {delay} 秒后重试(第 {attempts} 次)")
time.sleep(delay)
return wrapper
return decorator
# 测试:模拟网络请求(随机失败)
import random
@retry(max_attempts=3, delay=2, exceptions=(ConnectionError,))
def fetch_data(url):
print(f"尝试请求 URL:{url}")
# 模拟50%概率失败
if random.random() < 0.5:
raise ConnectionError("网络连接超时")
return f"成功获取 {url} 的数据"
# 调用函数
print(fetch_data("https://api.example.com/data"))
运行结果(可能出现的情况):
尝试请求 URL:https://api.example.com/data
WARNING:root:函数 fetch_data 执行失败,将在 2 秒后重试(第 1 次)
尝试请求 URL:https://api.example.com/data
WARNING:root:函数 fetch_data 执行失败,将在 2 秒后重试(第 2 次)
尝试请求 URL:https://api.example.com/data
成功获取 https://api.example.com/data 的数据
4.8 场景 8:事务管理装饰器
在数据库操作中,确保一系列操作要么全部成功(提交事务),要么全部失败(回滚事务)。
import functools
# 模拟数据库连接和事务
class Database:
def __init__(self):
self.connected = True
def begin_transaction(self):
print("开始事务")
def commit(self):
print("提交事务")
def rollback(self):
print("回滚事务")
# 全局数据库实例
db = Database()
def transaction_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
db.begin_transaction()
result = func(*args, **kwargs)
db.commit()
print("事务执行成功")
return result
except Exception as e:
db.rollback()
print(f"事务执行失败,已回滚:{str(e)}")
raise
return wrapper
# 测试:数据库操作
@transaction_decorator
def transfer_money(from_user, to_user, amount):
print(f"从 {from_user} 扣除 {amount} 元")
print(f"向 {to_user} 添加 {amount} 元")
# 模拟异常(比如余额不足)
if amount > 1000:
raise ValueError("转账金额超过限额")
# 正常转账
transfer_money("张三", "李四", 500)
print("-" * 20)
# 异常转账(触发回滚)
try:
transfer_money("张三", "李四", 2000)
except ValueError as e:
pass
运行结果:
开始事务
从 张三 扣除 500 元
向 李四 添加 500 元
提交事务
事务执行成功
--------------------
开始事务
从 张三 扣除 2000 元
向 李四 添加 2000 元
回滚事务
事务执行失败,已回滚:转账金额超过限额
4.9 场景 9:限流装饰器
限制函数在单位时间内的调用次数,避免高并发场景下的系统过载(适用于接口限流)。
import functools
import time
from collections import defaultdict
# 存储函数调用时间记录:{func_name: [call_times]}
call_records = defaultdict(list)
def rate_limit(limit=10, period=1):
"""限流装饰器:period秒内最多调用limit次"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
func_name = func.__name__
# 清理过期的调用记录
call_records[func_name] = [t for t in call_records[func_name] if now - t < period]
# 检查调用次数是否超过限制
if len(call_records[func_name]) >= limit:
raise TooManyRequestsError(f"函数 {func_name} 调用过于频繁!{period}秒内最多调用{limit}次")
# 记录当前调用时间
call_records[func_name].append(now)
return func(*args, **kwargs)
return wrapper
return decorator
# 自定义异常:调用过于频繁
class TooManyRequestsError(Exception):
pass
# 测试:限流控制
@rate_limit(limit=3, period=5) # 5秒内最多调用3次
def query_data():
print("查询数据成功")
# 连续调用4次
for i in range(4):
try:
query_data()
print(f"第 {i+1} 次调用成功")
except TooManyRequestsError as e:
print(f"第 {i+1} 次调用失败:{e}")
time.sleep(1)
运行结果:
查询数据成功
第 1 次调用成功
查询数据成功
第 2 次调用成功
查询数据成功
第 3 次调用成功
第 4 次调用失败:函数 query_data 调用过于频繁!5秒内最多调用3次
4.10 场景 10:异步函数装饰器
Python 3.5 + 支持异步函数(async def),装饰器需要适配异步逻辑。
import functools
import asyncio
import time
def async_log_decorator(func):
# 验证被装饰函数是否为异步函数
if not asyncio.iscoroutinefunction(func):
raise TypeError("被装饰函数必须是异步函数(async def)")
@functools.wraps(func)
async def wrapper(*args, **kwargs):
print(f"[异步日志] 函数 {func.__name__} 开始执行")
start_time = time.time()
result = await func(*args, **kwargs) # 等待异步函数执行
end_time = time.time()
print(f"[异步日志] 函数 {func.__name__} 执行完成,耗时:{end_time - start_time:.4f}秒")
return result
return wrapper
# 测试:异步函数
@async_log_decorator
async def async_task():
"""模拟异步耗时操作"""
await asyncio.sleep(1)
print("异步任务执行完成")
return "异步任务结果"
# 运行异步函数
asyncio.run(async_task())
运行结果:
[异步日志] 函数 async_task 开始执行
异步任务执行完成
[异步日志] 函数 async_task 执行完成,耗时:1.0012秒
五、避坑指南:装饰器开发的 10 个常见错误
5.1 错误 1:忘记保留被装饰函数的元信息
问题:装饰器的包装函数覆盖了被装饰函数的__name__、__doc__等元信息。解决方案:使用functools.wraps(func)装饰包装函数。
5.2 错误 2:被装饰函数有返回值但未处理
问题:包装函数未返回被装饰函数的结果,导致调用者无法获取返回值。解决方案:在包装函数中保存func(*args, **kwargs)的结果,并返回。
5.3 错误 3:带参数的装饰器实现层级错误
问题:混淆了装饰器参数、被装饰函数、包装函数的层级关系。解决方案:带参数的装饰器需三层嵌套:参数接收层→函数接收层→包装逻辑层。
5.4 错误 4:装饰器嵌套执行顺序理解错误
问题:多个装饰器嵌套时,执行顺序不符合预期。解决方案:记住 "就近原则":靠近函数的装饰器先执行,远离函数的后执行(等价于函数嵌套调用)。
5.5 错误 5:异步函数使用同步装饰器
问题:用同步装饰器装饰异步函数,导致await关键字无法使用,或函数无法正常执行。解决方案:异步函数装饰器的包装函数必须是异步的(async def),并使用await调用被装饰函数。
5.6 错误 6:缓存装饰器用于不可哈希参数
问题:将缓存装饰器用于参数为列表、字典等不可哈希类型的函数,导致报错。解决方案:确保缓存的 key 是可哈希的,或在装饰器中处理不可哈希参数(如转换为字符串)。
5.7 错误 7:装饰器导致函数递归调用自身
问题:在装饰器中直接调用func(),而func已被装饰器替换,导致递归调用。解决方案:确保在包装函数中调用的是原始的func(未被装饰的函数)。
5.8 错误 8:权限验证装饰器未处理用户上下文
问题:装饰器中硬编码用户信息,无法适应多用户场景。解决方案:从全局上下文、函数参数或配置中动态获取用户信息。
5.9 错误 9:重试装饰器未指定异常类型
问题:重试装饰器捕获所有异常,包括逻辑错误(如参数错误),导致无效重试。解决方案:通过exceptions参数指定需要重试的异常类型。
5.10 错误 10:装饰器的副作用未清理
问题:装饰器中创建的资源(如文件句柄、数据库连接)未及时清理,导致资源泄露。解决方案:使用try-finally或上下文管理器确保资源清理。
六、实战项目:用装饰器打造一个简易接口框架

结合前面的知识点,我们用装饰器开发一个简易的 HTTP 接口框架,支持路由注册、权限验证、日志记录、参数校验等核心功能。
6.1 项目结构
simple_api/
└── main.py # 框架核心代码
6.2 完整代码实现
import functools
import logging
import json
from typing import Dict, Type
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
# 全局变量:存储路由映射和权限配置
routes = {} # {url: (func, required_role)}
current_user = {"username": "admin", "role": "admin"} # 模拟当前用户
# ------------------------------
# 装饰器1:路由注册
# ------------------------------
def route(url: str, required_role: str = "guest"):
"""路由注册装饰器:绑定URL与处理函数,并指定所需权限"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
# 注册路由
routes[url] = (wrapper, required_role)
logging.info(f"注册路由:{url},所需权限:{required_role}")
return wrapper
return decorator
# ------------------------------
# 装饰器2:权限验证
# ------------------------------
def permission_check(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 获取当前路由的所需权限
url = args[0] # 假设第一个参数是URL
_, required_role = routes[url]
if current_user["role"] != required_role:
return json.dumps({"code": 403, "msg": f"权限不足,需要{required_role}角色"})
logging.info(f"用户 {current_user['username']} 权限验证通过")
return func(*args, **kwargs)
return wrapper
# ------------------------------
# 装饰器3:日志记录
# ------------------------------
def api_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
url = args[0]
logging.info(f"收到请求:{url},参数:{kwargs}")
start_time = time.time()
try:
result = func(*args, **kwargs)
logging.info(f"请求 {url} 处理成功,耗时:{time.time() - start_time:.4f}秒")
return result
except Exception as e:
logging.error(f"请求 {url} 处理失败:{str(e)}")
return json.dumps({"code": 500, "msg": "服务器内部错误"})
return wrapper
# ------------------------------
# 装饰器4:参数校验
# ------------------------------
def validate_params(param_rules: Dict[str, Type]):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 验证参数
for param_name, param_type in param_rules.items():
if param_name not in kwargs:
return json.dumps({"code": 400, "msg": f"缺少参数:{param_name}"})
if not isinstance(kwargs[param_name], param_type):
return json.dumps({
"code": 400,
"msg": f"参数 {param_name} 类型错误,预期 {param_type.__name__}"
})
return func(*args, **kwargs)
return wrapper
return decorator
# ------------------------------
# 核心:请求处理函数
# ------------------------------
@api_log
@permission_check
def handle_request(url: str, **kwargs):
"""处理HTTP请求:根据URL路由到对应的处理函数"""
if url not in routes:
return json.dumps({"code": 404, "msg": "接口不存在"})
func, _ = routes[url]
return func(url, **kwargs)
# ------------------------------
# 定义接口(使用装饰器)
# ------------------------------
# 1. 公开接口(无需特殊权限)
@route("/api/hello", required_role="guest")
def hello_handler(url, **kwargs):
name = kwargs.get("name", "匿名用户")
return json.dumps({"code": 200, "msg": f"Hello, {name}!"})
# 2. 需要管理员权限的接口
@route("/api/admin/delete", required_role="admin")
@validate_params({"id": int})
def delete_handler(url, **kwargs):
id = kwargs["id"]
return json.dumps({"code": 200, "msg": f"成功删除ID为{id的数据}"})
# 3. 需要编辑者权限的接口
@route("/api/editor/edit", required_role="editor")
@validate_params({"id": int, "content": str})
def edit_handler(url, **kwargs):
id = kwargs["id"]
content = kwargs["content"]
return json.dumps({"code": 200, "msg": f"成功编辑ID为{id}的数据,内容:{content}"})
# ------------------------------
# 测试接口
# ------------------------------
if __name__ == "__main__":
import time
# 测试1:访问公开接口
print("测试1:访问公开接口")
print(handle_request("/api/hello", name="Python"))
print("-" * 30)
# 测试2:管理员访问删除接口(参数正确)
print("测试2:管理员访问删除接口")
print(handle_request("/api/admin/delete", id=1001))
print("-" * 30)
# 测试3:管理员访问删除接口(参数错误)
print("测试3:管理员访问删除接口(参数错误)")
print(handle_request("/api/admin/delete", id="abc"))
print("-" * 30)
# 测试4:普通用户访问管理员接口(权限不足)
current_user["role"] = "guest"
print("测试4:普通用户访问管理员接口")
print(handle_request("/api/admin/delete", id=1001))
print("-" * 30)
# 测试5:访问不存在的接口
print("测试5:访问不存在的接口")
print(handle_request("/api/nonexistent"))
6.3 运行结果
2024-05-20 15:00:00,000 - INFO - 注册路由:/api/hello,所需权限:guest
2024-05-20 15:00:00,000 - INFO - 注册路由:/api/admin/delete,所需权限:admin
2024-05-20 15:00:00,000 - INFO - 注册路由:/api/editor/edit,所需权限:editor
测试1:访问公开接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/hello,参数:{'name': 'Python'}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/hello 处理成功,耗时:0.0000秒
{"code": 200, "msg": "Hello, Python!"}
------------------------------
测试2:管理员访问删除接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 1001}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 200, "msg": "成功删除ID为1001的数据"}
------------------------------
测试3:管理员访问删除接口(参数错误)
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 'abc'}
2024-05-20 15:00:00,000 - INFO - 用户 admin 权限验证通过
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 400, "msg": "参数 id 类型错误,预期 int"}
------------------------------
测试4:普通用户访问管理员接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/admin/delete,参数:{'id': 1001}
2024-05-20 15:00:00,000 - INFO - 请求 /api/admin/delete 处理成功,耗时:0.0000秒
{"code": 403, "msg": "权限不足,需要admin角色"}
------------------------------
测试5:访问不存在的接口
2024-05-20 15:00:00,000 - INFO - 收到请求:/api/nonexistent,参数:{}
2024-05-20 15:00:00,000 - INFO - 请求 /api/nonexistent 处理成功,耗时:0.0000秒
{"code": 404, "msg": "接口不存在"}
6.4 项目亮点
- 装饰器解耦:路由、权限、日志、参数校验等功能通过装饰器实现,与业务逻辑分离;
- 可扩展性强:新增接口只需添加
@route装饰器,新增功能只需添加对应的装饰器; - 模拟真实场景:涵盖 Web 开发中常见的核心功能,可直接扩展为真实的 HTTP 接口服务。
七、总结与进阶学习

7.1 装饰器核心要点总结
- 本质:装饰器是 "函数包装器",通过函数嵌套 / 类实现,核心是 "无侵入式增强函数功能";
- 核心特性:不修改原函数代码、不改变函数调用方式,支持嵌套、参数传递、元信息保留;
- 应用场景:日志记录、性能计时、缓存控制、权限验证、参数校验、异常处理等;
- 关键工具:
functools.wraps(保留元信息)、functools.lru_cache(内置缓存装饰器)。
7.2 进阶学习方向
- 深入学习
functools模块:掌握partial(函数参数绑定)、singledispatch(单分派泛型函数)等高级工具; - 框架源码中的装饰器:阅读 Django、Flask、FastAPI 等框架的源码,学习装饰器在实际框架中的应用;
- 自定义带状态的装饰器:结合类装饰器和闭包,实现更复杂的状态管理(如限流装饰器的调用次数统计);
- 装饰器与设计模式:理解装饰器模式与代理模式、适配器模式的区别与联系;
- 异步装饰器进阶:学习
asyncio模块的高级特性,实现支持异步上下文的装饰器。
7.3 学习建议
装饰器的学习重点在于 "理解原理 + 多练实战"。建议先从简单的函数装饰器入手,掌握基础逻辑后,逐步尝试带参数的装饰器、类装饰器,最后通过实战项目整合所学知识。
记住:装饰器的核心价值是 "解耦"—— 将通用功能与业务逻辑分离,让代码更简洁、更易维护。在实际开发中,不要为了使用装饰器而使用,而是当遇到 "多个函数需要相同增强功能" 时,再考虑用装饰器解决。


被折叠的 条评论
为什么被折叠?



