在 Python 中,装饰器(Decorator) 是一种强大的语法工具,用于在不修改原函数 / 类代码的前提下,为其添加额外功能(如日志记录、性能计时、权限校验、缓存等)。它本质上是一个接收函数 / 类作为参数,并返回新函数 / 类的高阶函数,通过 @ 语法糖简化使用,极大提升了代码的复用性和可读性。
一、装饰器的核心本质
装饰器的核心逻辑可以概括为:“包裹原函数,在不改变其核心逻辑的情况下,在执行前后插入额外操作”。
从技术角度看,装饰器是一个满足以下条件的函数:
- 接收一个函数(或类)作为参数;
- 内部定义一个 “包装函数(wrapper)”,用于包裹原函数并添加额外逻辑;
- 返回这个包装函数(替代原函数)。
二、基础装饰器:无参数装饰器
先从最简单的 “无参数装饰器” 入手,理解其基本工作原理。
1. 定义与使用
假设我们需要为函数添加 “执行前后打印日志” 的功能,用装饰器实现如下:
# 定义装饰器:为函数添加日志功能
def log_decorator(func):
# 定义包装函数(wrapper),用于包裹原函数
def wrapper():
print(f"[日志] 开始执行函数:{func.__name__}") # 执行前的额外操作
result = func() # 调用原函数
print(f"[日志] 函数 {func.__name__} 执行结束") # 执行后的额外操作
return result # 返回原函数的结果
return wrapper # 返回包装函数
# 使用装饰器:用 @ 语法糖将装饰器应用到目标函数
@log_decorator
def say_hello():
print("Hello, 装饰器!")
# 调用被装饰后的函数
say_hello()
输出结果:
[日志] 开始执行函数:say_hello
Hello, 装饰器!
[日志] 函数 say_hello 执行结束
2. 执行流程解析
@log_decorator 是语法糖,等价于:say_hello = log_decorator(say_hello)
整个过程分为三步:
- 定义
say_hello函数时,@log_decorator触发log_decorator(say_hello)调用; log_decorator接收say_hello作为参数,定义wrapper函数并返回;- 原
say_hello被重新赋值为wrapper函数,因此调用say_hello()实际执行的是wrapper()。
3. 适配带参数的函数
上面的装饰器只能装饰无参数函数,若目标函数有参数(如 def add(a, b): ...),则需要让 wrapper 支持参数传递。通过 *args 和 **kwargs 可让 wrapper 接收任意数量的位置参数和关键字参数,实现通用装饰器:
def log_decorator(func):
# wrapper 用 *args 和 **kwargs 接收任意参数
def wrapper(*args, **kwargs):
print(f"[日志] 开始执行 {func.__name__},参数:{args}, {kwargs}")
result = func(*args, **kwargs) # 传递参数给原函数
print(f"[日志] {func.__name__} 执行结束,结果:{result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
@log_decorator
def greet(name, prefix="Hello"):
return f"{prefix}, {name}!"
# 调用测试
print(add(2, 3)) # 带位置参数
print(greet("Alice", prefix="Hi")) # 带位置+关键字参数
输出结果:
[日志] 开始执行 add,参数:(2, 3), {}
[日志] add 执行结束,结果:5
5
[日志] 开始执行 greet,参数:('Alice',), {'prefix': 'Hi'}
[日志] greet 执行结束,结果:Hi, Alice!
Hi, Alice!
通过 *args 和 **kwargs,装饰器可适配任意参数的函数,通用性极大提升。
三、带参数的装饰器
有时需要让装饰器 “可配置”(如指定日志级别、缓存过期时间等),此时需定义带参数的装饰器。其核心是在基础装饰器外层再嵌套一层函数,用于接收装饰器参数。
1. 定义与使用
例如,实现一个可指定日志级别的装饰器:
# 带参数的装饰器:外层函数接收装饰器参数
def log_decorator(level="INFO"):
# 中间层函数接收目标函数
def decorator(func):
# 内层 wrapper 函数包裹原函数
def wrapper(*args, **kwargs):
print(f"[{level}] 开始执行 {func.__name__}") # 使用装饰器参数
result = func(*args, **kwargs)
print(f"[{level}] {func.__name__} 执行结束")
return result
return wrapper
return decorator
# 使用带参数的装饰器:指定 level 为 "DEBUG"
@log_decorator(level="DEBUG")
def multiply(a, b):
return a * b
# 不指定参数,使用默认值 "INFO"
@log_decorator()
def subtract(a, b):
return a - b
# 调用测试
print(multiply(3, 4))
print(subtract(10, 5))
输出结果:
[DEBUG] 开始执行 multiply
[DEBUG] multiply 执行结束
12
[INFO] 开始执行 subtract
[INFO] subtract 执行结束
5
2. 执行流程解析
@log_decorator(level="DEBUG") 等价于:multiply = log_decorator(level="DEBUG")(multiply)
过程分为四步:
- 调用
log_decorator(level="DEBUG"),返回中间层函数decorator; - 调用
decorator(multiply),接收目标函数multiply; - 定义
wrapper并返回; - 原
multiply被赋值为wrapper,调用时执行wrapper。
四、保留原函数的元信息
装饰器会默认 “覆盖” 原函数的元信息(如 __name__、__doc__ 等),导致调试困难。例如:
def decorator(func):
def wrapper():
"""wrapper 函数的文档字符串"""
func()
return wrapper
@decorator
def original_func():
"""original_func 函数的文档字符串"""
print("原函数")
print(original_func.__name__) # 输出:wrapper(被覆盖)
print(original_func.__doc__) # 输出:wrapper 函数的文档字符串(被覆盖)
解决方法:使用 functools.wraps 装饰 wrapper,保留原函数的元信息:
import functools
def decorator(func):
# 用 functools.wraps 保留原函数元信息
@functools.wraps(func)
def wrapper():
"""wrapper 函数的文档字符串"""
func()
return wrapper
@decorator
def original_func():
"""original_func 函数的文档字符串"""
print("原函数")
print(original_func.__name__) # 输出:original_func(正确保留)
print(original_func.__doc__) # 输出:original_func 函数的文档字符串(正确保留)
functools.wraps 是装饰器开发的最佳实践,必须添加以确保元信息正确。
五、多个装饰器的叠加使用
一个函数可以同时应用多个装饰器,执行顺序为从下到上包裹(即靠近函数的装饰器先执行)。
import functools
# 装饰器1:打印执行时间
def timer_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"[计时] {func.__name__} 执行耗时:{end - start:.4f}秒")
return result
return wrapper
# 装饰器2:打印日志
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[日志] 开始执行 {func.__name__}")
result = func(*args, **kwargs)
print(f"[日志] {func.__name__} 执行结束")
return result
return wrapper
# 叠加使用装饰器:先执行 log_decorator,再执行 timer_decorator
@timer_decorator
@log_decorator
def slow_func(seconds):
import time
time.sleep(seconds) # 模拟耗时操作
return "完成"
slow_func(1)
输出结果:
[日志] 开始执行 slow_func # log_decorator 的前置操作
[日志] slow_func 执行结束 # log_decorator 的后置操作
[计时] slow_func 执行耗时:1.0012秒 # timer_decorator 的后置操作
执行顺序解析:@timer_decorator 和 @log_decorator 等价于:slow_func = timer_decorator(log_decorator(slow_func))因此,调用时的顺序是:timer_decorator 的 wrapper → log_decorator 的 wrapper → 原 slow_func。
六、类装饰器
除了装饰函数,装饰器也可以装饰类(通过接收类作为参数,返回新类)。类装饰器常用于动态修改类的属性或方法。
1. 基础类装饰器
def add_attr_decorator(cls):
# 为类添加新属性
cls.version = "1.0"
# 为类添加新方法
def new_method(self):
return f"我是 {self.__class__.__name__} 的新方法"
cls.new_method = new_method
return cls
@add_attr_decorator
class MyClass:
def __init__(self, name):
self.name = name
# 测试被装饰的类
obj = MyClass("测试")
print(obj.version) # 输出:1.0(新增的属性)
print(obj.new_method()) # 输出:我是 MyClass 的新方法(新增的方法)
2. 带参数的类装饰器
类似函数装饰器,类装饰器也可带参数(外层函数接收参数,内层函数处理类):
def add_prefix_decorator(prefix):
def decorator(cls):
# 为类的方法添加前缀
class WrappedClass(cls):
def greet(self):
return f"{prefix}: {super().greet()}" # 调用父类方法
return WrappedClass
return decorator
@add_prefix_decorator(prefix="【提示】")
class Greeting:
def greet(self):
return "Hello"
obj = Greeting()
print(obj.greet()) # 输出:【提示】: Hello
七、装饰器的典型应用场景
装饰器在实际开发中应用广泛,以下是常见场景:
1. 日志记录
为函数添加输入参数、返回值、执行时间等日志,便于调试和监控:
import functools
import logging
logging.basicConfig(level=logging.INFO)
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"调用 {func.__name__},参数:{args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 返回:{result}")
return result
return wrapper
@log
def calculate(a, b):
return a + b
2. 性能计时
统计函数执行时间,用于性能分析:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 耗时:{end - start:.6f}秒")
return result
return wrapper
@timer
def big_loop(n):
for i in range(n):
pass
3. 权限验证
在接口调用前验证用户权限,如登录状态、角色权限等:
import functools
def require_login(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_login:
raise PermissionError("请先登录")
return func(user, *args, **kwargs)
return wrapper
@require_login
def view_profile(user):
return f"用户 {user.name} 的个人资料"
4. 缓存(记忆化)
缓存函数的计算结果,避免重复计算(适用于耗时且输入固定的函数):
import functools
def cache(func):
cache_dict = {} # 缓存字典:key=参数,value=结果
@functools.wraps(func)
def wrapper(*args):
if args not in cache_dict:
cache_dict[args] = func(*args) # 计算并缓存结果
print(f"缓存 {args} 的结果")
else:
print(f"使用 {args} 的缓存结果")
return cache_dict[args]
return wrapper
@cache
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
fib(5) # 首次计算,缓存中间结果
fib(5) # 直接使用缓存
八、装饰器的最佳实践
- 始终使用
functools.wraps:保留原函数的元信息(__name__、__doc__等),避免调试混乱。 - 单一职责:一个装饰器只做一件事(如日志、计时、权限验证分开实现),提高复用性。
- 避免过度装饰:过多装饰器会增加函数调用栈复杂度,降低可读性。
- 文档说明:为装饰器添加文档字符串,说明其功能和参数(尤其是带参数的装饰器)。
- 处理异常:若装饰器可能触发异常(如权限验证失败),需明确抛出并说明。
总结
装饰器是 Python 中极具特色的语法,其核心是通过 “包装函数” 在不修改原代码的情况下扩展功能。从基础的无参数装饰器,到带参数的装饰器、类装饰器,再到多装饰器叠加,掌握这些用法能极大提升代码的模块化和复用性。