Python 装饰器:优雅增强函数功能的魔法工具
一、为什么需要装饰器?
在软件开发中,我们经常需要为函数添加额外功能,比如日志记录、性能统计、权限验证等。如果直接修改函数代码,会违反「开闭原则」(对扩展开放,对修改关闭),导致代码冗余且难以维护。Python 装饰器(Decorator)正是为解决这类问题而生的语法糖,它能在不改变原函数代码的前提下,动态增强函数功能。
二、装饰器基础:高阶函数与闭包
1. 高阶函数(Higher-Order Function)
满足以下至少一个条件的函数称为高阶函数:
- 接收一个或多个函数作为参数
- 返回一个函数作为结果
示例:接收函数作为参数
def greet(func):
func()
def say_hello():
print("Hello!")
greet(say_hello) # 输出:Hello!
2. 闭包(Closure)
如果内部函数引用了外部函数的变量,并且外部函数返回了内部函数,那么这个内部函数就形成了闭包。闭包允许函数在外部作用域之外访问内部变量。
示例:闭包实现计数器
def counter():
count = 0
def increment():
nonlocal count # 声明使用外部函数的变量
count += 1
print(count)
return increment
counter_func = counter()
counter_func() # 输出:1
counter_func() # 输出:2
三、装饰器的本质:用闭包包装函数
装饰器本质上是一个高阶函数,它接收一个函数作为输入,返回一个增强后的新函数作为输出,新函数通常会在执行原函数前后添加额外逻辑。
1. 无参数函数的装饰器
需求:为函数添加日志记录,打印函数执行前后的信息
def log_decorator(func):
def wrapper():
print(f"开始执行 {func.__name__}")
func() # 调用原函数
print(f"{func.__name__} 执行完毕")
return wrapper
@log_decorator # 等价于 say_hello = log_decorator(say_hello)
def say_hello():
print("Hello, Decorator!")
say_hello()
输出:
开始执行 say_hello
Hello, Decorator!
say_hello 执行完毕
2. 处理带参数的函数
如果被装饰的函数有参数,需要让 wrapper 函数接收任意参数:
def log_decorator(func):
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
add(2, 3)
输出:
开始执行 add,参数:(2, 3), {}
add 执行完毕,返回值:5
四、进阶用法:带参数的装饰器与类装饰器
1. 带参数的装饰器(三层函数结构)
如果装饰器本身需要接收参数(如日志级别、路径等),需要再嵌套一层函数:
def log_decorator_with_param(level): # 装饰器参数
def decorator(func): # 接收被装饰的函数
def wrapper(*args, **kwargs):
print(f"[{level}] 开始执行 {func.__name__}")
func(*args, **kwargs)
print(f"[{level}] {func.__name__} 执行完毕")
return wrapper
return decorator # 返回真正的装饰器
@log_decorator_with_param(level="DEBUG") # 等价于 say_hello = log_decorator_with_param("DEBUG")(say_hello)
def say_hello():
print("Hello!")
say_hello()
输出:
[DEBUG] 开始执行 say_hello
Hello!
[DEBUG] say_hello 执行完毕
2. 类装饰器(使用 __call__ 方法)
除了函数装饰器,也可以用类实现装饰器,通过 __call__ 方法让实例可调用:
class ClassDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"类装饰器开始执行 {self.func.__name__}")
self.func(*args, **kwargs)
print(f"类装饰器执行完毕")
@ClassDecorator
def say_bye():
print("Goodbye!")
say_bye()
输出:
类装饰器开始执行 say_bye
Goodbye!
类装饰器执行完毕
五、关键工具: functools.wraps 保留函数元信息
装饰器会导致原函数的元信息(如名称、文档字符串)被覆盖,可通过 functools.wraps 修复:
from functools import wraps
def log_decorator(func):
@wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
"""包装函数的文档字符串"""
print(f"执行 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def say_hello():
"""这是一个打招呼的函数"""
print("Hello!")
print(say_hello.__name__) # 输出:say_hello(而非 wrapper)
print(say_hello.__doc__) # 输出:这是一个打招呼的函数(而非包装函数的文档)
六、常见应用场景
1. 日志记录
记录函数的调用时间、参数、返回值,方便调试和监控。
2. 性能分析
计算函数执行耗时,定位性能瓶颈:
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时 {time.time() - start:.4f} 秒")
return result
return wrapper
3. 权限验证
在函数执行前检查用户权限,避免非法调用:
def require_auth(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not current_user.is_authenticated:
raise PermissionError("需要登录才能访问")
return func(*args, **kwargs)
return wrapper
4. 缓存结果
使用 lru_cache 装饰器缓存函数结果,避免重复计算(Python 内置装饰器):
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
七、最佳实践与注意事项
1. 保持装饰器的纯度:避免在装饰器中修改被装饰函数的内部状态,专注于功能增强。
2. 合理使用嵌套:带参数的装饰器可能导致多层嵌套,保持代码简洁,必要时添加注释。
3. 注意执行时机:装饰器在函数定义时立即执行(加载时),而非调用时。
4. 优先使用内置装饰器:如 @property 、 @classmethod 、 @staticmethod 等,遵循 Python 惯例。
八、总结
装饰器是 Python 中极具灵活性的特性,它通过高阶函数和闭包实现了代码的横向扩展,让我们可以优雅地复用逻辑、分离关注点。掌握装饰器的核心原理(高阶函数、闭包、元编程)后,你可以在日志、性能、权限等场景中高效提升代码质量。记住用 functools.wraps 保留函数元信息,让代码更健壮易读。多写多练,你会逐渐体会到装饰器带来的编程乐趣!