Python 装饰器完全指南
Python装饰器是一种强大的编程工具,通过高阶函数和闭包实现对函数/类的非侵入式功能扩展。本文系统讲解了装饰器的核心概念、实现原理和进阶用法:从基础的无参装饰器到带参数的装饰器,从函数装饰器到类装饰器,并提供了日志记录、权限验证、缓存优化等实战场景应用。文章强调使用@wraps保留元信息、多层嵌套实现参数传递等最佳实践,以及避免常见错误的避坑指南。掌握装饰器能显著提升代码复用性和可维护性,是Py
Python 装饰器完全指南:从入门到实战的优雅编程技巧
在 Python 中,装饰器(Decorator)是一种强大的编程工具,它允许你在不修改原函数 / 类代码的前提下,动态增强其功能。无论是日志记录、性能计时、权限验证,还是缓存优化,装饰器都能让代码更简洁、复用性更强,是「面向切面编程(AOP)」的核心实现方式。
本文将从「基础原理→逐步实现→进阶用法→实战场景」,带你彻底掌握 Python 装饰器,即使是新手也能轻松上手。
一、装饰器的核心概念:为什么需要它?
1. 先看一个痛点场景
假设你已经写好了多个业务函数,现在需要给每个函数添加「执行日志」功能(打印函数名和执行时间):
# 原有业务函数
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def subtract(a, b):
return a - b
如果直接修改函数代码,会导致两个问题:
- 代码冗余:每个函数都要写重复的日志逻辑;
- 侵入性强:破坏了原函数的纯粹性,后续想移除日志功能需要逐个修改。
2. 装饰器的解决方案
装饰器的核心思想是「包装器」:用一个函数 / 类去包裹原函数 / 类,在不改变其内部逻辑的前提下,添加额外功能。
使用装饰器后,效果如下:
# 定义装饰器(日志功能)
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"函数 {func.__name__} 开始执行...")
result = func(*args, **kwargs) # 调用原函数
print(f"函数 {func.__name__} 执行结束")
return result
return wrapper
# 用 @ 语法糖应用装饰器
@log_decorator
def add(a, b):
return a + b
@log_decorator
def multiply(a, b):
return a * b
# 调用函数(无需修改调用方式)
print(add(2, 3)) # 自动打印日志,输出 5
print(multiply(2, 3)) # 自动打印日志,输出 6
优势:
- 无侵入:原函数代码完全不变;
- 可复用:装饰器可直接应用于任意函数;
- 灵活性高:随时可以添加 / 移除装饰器。
3. 装饰器的本质
装饰器本质是「高阶函数 + 函数嵌套 + 闭包」的组合:
- 高阶函数:接收函数作为参数,或返回函数作为结果;
- 函数嵌套:在函数内部定义另一个函数;
- 闭包:内层函数可以访问外层函数的变量(如上述
func)。
二、基础入门:一步步实现装饰器
在写装饰器之前,必须先掌握 Python 的一个核心特性:函数是一等对象(可以作为参数、返回值,也可以赋值给变量)。
1. 铺垫:函数是一等对象
# 1. 函数可以赋值给变量
def hello():
print("Hello, Decorator!")
greet = hello # 变量 greet 指向 hello 函数
greet() # 输出:Hello, Decorator!
# 2. 函数可以作为参数传递
def wrapper(func):
func() # 调用传入的函数
wrapper(hello) # 输出:Hello, Decorator!
# 3. 函数可以作为返回值
def create_func():
def inner():
print("我是内部函数")
return inner # 返回内部函数
f = create_func()
f() # 输出:我是内部函数
2. 第一步:手动实现简单装饰器
基于上述特性,我们手动实现一个「计时装饰器」(统计函数执行时间):
import time
# 1. 定义装饰器函数(接收原函数作为参数)
def timer_decorator(func):
# 2. 定义嵌套函数(包裹原函数,添加额外功能)
def wrapper():
start_time = time.time() # 额外功能:记录开始时间
func() # 调用原函数
end_time = time.time() # 额外功能:记录结束时间
print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
# 3. 返回嵌套函数(替换原函数)
return wrapper
# 定义原函数
def slow_func():
time.sleep(1) # 模拟耗时操作
print("耗时函数执行完成")
# 4. 手动应用装饰器(等价于 @timer_decorator)
slow_func = timer_decorator(slow_func)
# 调用函数
slow_func()
# 输出:
# 耗时函数执行完成
# 函数 slow_func 执行耗时:1.0005 秒
3. 第二步:使用语法糖 @(简化装饰器应用)
Python 提供了 @装饰器名 语法糖,让装饰器的应用更简洁,无需手动赋值:
import time
def timer_decorator(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print(f"执行耗时:{end_time - start_time:.4f} 秒")
return wrapper
# 用 @ 语法糖应用装饰器(等价于 slow_func = timer_decorator(slow_func))
@timer_decorator
def slow_func():
time.sleep(1)
print("耗时函数执行完成")
slow_func() # 效果和手动应用一致
4. 第三步:兼容带参数的函数
上述装饰器只能用于无参函数,若原函数有参数(如 add(a, b)),调用时会报错。解决方法是让嵌套函数 wrapper 接收任意参数:
import time
def timer_decorator(func):
# 用 *args 接收位置参数,**kwargs 接收关键字参数
def wrapper(*args, **kwargs):
start_time = time.time()
# 传递参数给原函数
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
return result # 返回原函数的结果
return wrapper
# 带参数的函数
@timer_decorator
def add(a, b):
time.sleep(0.5)
return a + b
# 调用带参数的函数
result = add(2, 3)
print(f"结果:{result}")
# 输出:
# 函数 add 执行耗时:0.5003 秒
# 结果:5
关键:*args 和 **kwargs 可以接收任意数量、任意类型的参数,确保装饰器兼容所有函数。
5. 第四步:保留原函数的元信息
装饰器会替换原函数,导致原函数的元信息(如 __name__、__doc__)丢失:
@timer_decorator
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出:wrapper(而非 add)
print(add.__doc__) # 输出:None(而非 "计算两个数的和")
解决方案:使用 functools.wraps 装饰器,自动复制原函数的元信息:
import time
from functools import wraps # 导入 wraps
def timer_decorator(func):
@wraps(func) # 关键:保留原函数元信息
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
return result
return wrapper
@timer_decorator
def add(a, b):
"""计算两个数的和"""
return a + b
print(add.__name__) # 输出:add(正确)
print(add.__doc__) # 输出:计算两个数的和(正确)
注意:这是装饰器的最佳实践,务必养成使用 @wraps(func) 的习惯。
三、进阶用法:带参数的装饰器
有时候我们需要让装饰器本身支持参数,比如「日志装饰器可配置是否打印时间戳」「权限装饰器可指定允许的角色」。
带参数的装饰器需要三层函数嵌套:
- 最外层函数:接收装饰器的参数;
- 中间层函数:接收原函数;
- 最内层函数:包裹原函数的执行逻辑。
示例:带参数的日志装饰器
import time
from functools import wraps
# 最外层:接收装饰器参数(enable_timestamp:是否打印时间戳)
def log_decorator(enable_timestamp=True):
# 中间层:接收原函数
def decorator(func):
@wraps(func)
# 最内层:包裹执行逻辑
def wrapper(*args, **kwargs):
# 使用装饰器参数
if enable_timestamp:
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"[{timestamp}] 函数 {func.__name__} 开始执行...")
else:
print(f"函数 {func.__name__} 开始执行...")
result = func(*args, **kwargs)
print(f"函数 {func.__name__} 执行结束")
return result
return wrapper
return decorator
# 应用装饰器(不传递参数,使用默认值 True)
@log_decorator()
def add(a, b):
return a + b
# 应用装饰器(传递参数 enable_timestamp=False)
@log_decorator(enable_timestamp=False)
def multiply(a, b):
return a * b
# 调用函数
print(add(2, 3))
# 输出:
# [2025-11-11 15:30:00] 函数 add 开始执行...
# 函数 add 执行结束
# 5
print(multiply(2, 3))
# 输出:
# 函数 multiply 开始执行...
# 函数 multiply 执行结束
# 6
执行流程:
@log_decorator(enable_timestamp=False)→ 先执行最外层函数,返回中间层decorator;decorator(multiply)→ 中间层接收原函数,返回内层wrapper;multiply = wrapper→ 调用multiply(2,3)实际执行wrapper(2,3)。
四、高级用法:类装饰器
除了函数装饰器,Python 还支持「类装饰器」—— 用类来实现装饰器,适合需要维护状态的场景(如统计函数调用次数、缓存结果)。
类装饰器的核心是实现 __call__ 方法:当装饰后的函数被调用时,会自动执行 __call__ 方法。
示例 1:统计函数调用次数(带状态的装饰器)
from functools import wraps
class CountDecorator:
def __init__(self, func):
# 初始化时接收原函数
self.func = func
self.count = 0 # 维护状态:记录调用次数
def __call__(self, *args, **kwargs):
# 函数被调用时执行
self.count += 1
print(f"函数 {self.func.__name__} 已调用 {self.count} 次")
result = self.func(*args, **kwargs)
return result
# 应用类装饰器(无需加括号,因为 __init__ 直接接收 func)
@CountDecorator
def add(a, b):
return a + b
# 多次调用
add(2, 3) # 输出:函数 add 已调用 1 次 → 结果 5
add(4, 5) # 输出:函数 add 已调用 2 次 → 结果 9
add(6, 7) # 输出:函数 add 已调用 3 次 → 结果 13
示例 2:带参数的类装饰器
如果类装饰器需要接收参数,需在 __init__ 中先接收装饰器参数,再通过 __call__ 接收原函数:
from functools import wraps
class CacheDecorator:
def __init__(self, expire=60):
# 接收装饰器参数:缓存过期时间(默认 60 秒)
self.expire = expire
self.cache = {} # 缓存存储:key=参数,value=(结果, 过期时间)
def __call__(self, func):
# 接收原函数,返回 wrapper
@wraps(func)
def wrapper(*args, **kwargs):
# 生成缓存 key(基于函数名和参数)
key = (func.__name__, args, frozenset(kwargs.items()))
now = time.time()
# 检查缓存是否存在且未过期
if key in self.cache:
result, expire_time = self.cache[key]
if now < expire_time:
print(f"命中缓存!返回缓存结果")
return result
else:
print(f"缓存已过期,重新计算")
# 缓存不存在/过期,执行原函数
result = func(*args, **kwargs)
# 存入缓存(设置过期时间)
self.cache[key] = (result, now + self.expire)
print(f"缓存已更新")
return result
return wrapper
# 应用带参数的类装饰器
@CacheDecorator(expire=30) # 缓存 30 秒
def slow_func(a):
time.sleep(1) # 模拟耗时操作
return a * 2
# 第一次调用:无缓存,耗时 1 秒
print(slow_func(2)) # 输出:缓存已更新 → 4
# 30 秒内第二次调用:命中缓存,瞬间返回
print(slow_func(2)) # 输出:命中缓存!返回缓存结果 → 4
五、多个装饰器的叠加使用
一个函数可以同时应用多个装饰器,执行顺序遵循「包裹顺序从下到上,执行顺序从上到下」。
示例:日志装饰器 + 计时装饰器
import time
from functools import wraps
# 日志装饰器
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"【日志】函数 {func.__name__} 开始执行")
result = func(*args, **kwargs)
print(f"【日志】函数 {func.__name__} 执行结束")
return result
return wrapper
# 计时装饰器
def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"【计时】执行耗时:{end - start:.4f} 秒")
return result
return wrapper
# 多个装饰器叠加(顺序:先 log,后 timer)
@timer_decorator # 上层装饰器
@log_decorator # 下层装饰器
def add(a, b):
time.sleep(0.5)
return a + b
# 调用函数
print(add(2, 3))
执行顺序解析:
- 装饰器应用顺序(包裹):
@log_decorator先包裹add,再被@timer_decorator包裹 → 最终add = timer_decorator(log_decorator(add)); - 函数执行顺序:
timer_decorator的wrapper→log_decorator的wrapper→ 原函数add→ 返回到log_decorator→ 返回到timer_decorator。
输出结果:
【计时】开始计时...
【日志】函数 add 开始执行
【日志】函数 add 执行结束
【计时】执行耗时:0.5003 秒
5
六、实战场景:装饰器的高频应用
装饰器在实际开发中应用广泛,以下是几个最常用的场景,直接复制即可使用。
1. 日志记录(含参数和异常捕获)
import time
import logging
from functools import wraps
# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
def log_with_exception(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
logging.info(f"函数 {func.__name__} 调用,参数:args={args}, kwargs={kwargs}")
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 # 重新抛出异常,不影响原逻辑
return wrapper
@log_with_exception
def divide(a, b):
return a / b
divide(10, 2) # 日志记录成功
divide(10, 0) # 日志记录异常,同时抛出 ZeroDivisionError
2. 权限验证(Web 开发常用)
from functools import wraps
# 模拟用户角色
current_user = {"name": "admin", "role": "admin"}
def permission_required(required_role):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 验证角色
if current_user["role"] != required_role:
raise PermissionError(f"权限不足!需要 {required_role} 角色")
return func(*args, **kwargs)
return wrapper
return decorator
# 仅 admin 角色可执行
@permission_required("admin")
def delete_data():
print("数据删除成功")
# 仅 user 角色可执行
@permission_required("user")
def view_data():
print("数据查看成功")
delete_data() # 执行成功
view_data() # 执行成功(admin 角色可兼容 user?可根据需求修改逻辑)
3. 缓存优化(避免重复计算)
from functools import wraps
import time
def cache_decorator(expire=60):
cache = {}
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
key = (func.__name__, args, frozenset(kwargs.items()))
now = time.time()
# 检查缓存
if key in cache and now < cache[key][1]:
return cache[key][0]
# 计算并缓存
result = func(*args, **kwargs)
cache[key] = (result, now + expire)
return result
return wrapper
return decorator
# 缓存 10 秒
@cache_decorator(expire=10)
def fibonacci(n):
"""计算斐波那契数列(递归方式,重复计算多)"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(30)) # 第一次计算耗时较长
print(fibonacci(30)) # 10 秒内命中缓存,瞬间返回
4. 函数重试(网络请求常用)
import time
from functools import wraps
def retry_decorator(max_retries=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
retries += 1
if retries == max_retries:
raise # 达到最大重试次数,抛出异常
print(f"执行失败({e}),{delay} 秒后重试...(剩余 {max_retries - retries} 次)")
time.sleep(delay)
return wrapper
return decorator
# 最多重试 3 次,每次间隔 2 秒
@retry_decorator(max_retries=3, delay=2)
def fetch_data():
"""模拟网络请求,随机失败"""
import random
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
print("数据获取成功")
fetch_data()
七、避坑指南:装饰器的常见问题
1. 装饰器改变原函数元信息
问题:如前文所述,装饰器会导致原函数的 __name__、__doc__ 等元信息丢失。解决方案:必须使用 from functools import wraps 装饰 wrapper 函数。
2. 装饰器应用顺序错误
问题:多个装饰器叠加时,顺序不当导致功能异常(如先计时后日志,会包含日志打印时间)。解决方案:按「功能逻辑顺序」排列,如「日志 → 计时 → 权限」,确保内层装饰器先执行核心功能。
3. 装饰器参数传递错误
问题:带参数的装饰器忘记加括号(如 @log_decorator 而非 @log_decorator()),导致 func 接收的是装饰器参数而非原函数。解决方案:带参数的装饰器必须加括号(即使无参数也需 @decorator()),无参数的装饰器不加括号。
4. 类方法 / 静态方法的装饰器
问题:装饰类方法时,未考虑 self 参数,导致报错。解决方案:装饰器的 wrapper 函数需用 *args 接收 self,且类方法装饰器应放在 @classmethod 或 @staticmethod 下方:
class MyClass:
@log_decorator # 放在 @classmethod 下方
@classmethod
def class_method(cls):
print("类方法")
@log_decorator
@staticmethod
def static_method():
print("静态方法")
八、总结
Python 装饰器是「优雅编程」的典范,其核心价值在于「无侵入式增强功能」和「代码复用」。
核心要点回顾:
- 基础结构:函数装饰器 = 高阶函数 + 嵌套函数 + 闭包,需用
@wraps保留元信息; - 进阶用法:带参数的装饰器需三层嵌套,类装饰器需实现
__call__方法; - 执行顺序:多个装饰器「下包上执行」;
- 实战场景:日志、计时、权限、缓存、重试等,覆盖大多数开发需求。
学习建议:
- 从简单装饰器(如日志、计时)入手,理解函数嵌套和闭包;
- 手动实现每个实战场景的装饰器,加深记忆;
- 在项目中尝试应用,逐步替换重复代码,感受装饰器的简洁之美。
装饰器的灵活运用能让你的 Python 代码更简洁、更高效、更具可维护性。掌握它,你将迈入 Python 进阶开发者的行列!
更多推荐



所有评论(0)