在Python中,函数是“一等公民”——它可以被当作参数传递、赋值给变量,甚至作为返回值。装饰器正是利用了这一特性,让我们能在不修改原函数代码的前提下,给函数“动态添加功能”。这就像给礼物盒套上一个漂亮的包装纸,既保留了礼物本身,又增添了新的外观。
一、为什么需要装饰器?¶
假设我们要给多个函数添加“打印执行日志”的功能。如果直接修改每个函数,代码会变得重复且难以维护。例如:
# 原始函数1
def add(a, b):
return a + b
# 原始函数2
def subtract(a, b):
return a - b
如果想给它们加日志,最直接的方式是手动修改每个函数:
# 加了日志的add函数
def add(a, b):
print(f"函数add开始执行,参数:a={a}, b={b}")
result = a + b
print(f"函数add执行结束,结果:{result}")
return result
# 加了日志的subtract函数(重复代码)
def subtract(a, b):
print(f"函数subtract开始执行,参数:a={a}, b={b}")
result = a - b
print(f"函数subtract执行结束,结果:{result}")
return result
这样做显然不优雅。如果有100个函数,就要重复写100遍日志代码。装饰器的出现,就是为了解决这种“重复造轮子”的问题。
二、装饰器的基本原理¶
装饰器本质是一个函数,它接收一个函数作为参数,并返回一个新函数(这个新函数“包装”了原函数,添加了额外功能)。
1. 最简单的装饰器¶
我们先定义一个“日志装饰器”,它能给任何函数添加“开始/结束执行”的日志:
def log_decorator(func): # 接收原函数func
def wrapper(*args, **kwargs): # 内部函数(包装器)
# 1. 添加功能:打印开始日志
print(f"函数 {func.__name__} 开始执行,参数:{args}, {kwargs}")
# 2. 调用原函数
result = func(*args, **kwargs)
# 3. 添加功能:打印结束日志
print(f"函数 {func.__name__} 执行结束,结果:{result}")
return result
return wrapper # 返回包装后的函数
2. 使用装饰器¶
用 @ 语法糖(装饰器语法)把原函数“包裹”起来:
@log_decorator # 相当于:add = log_decorator(add)
def add(a, b):
return a + b
@log_decorator # 同理:subtract = log_decorator(subtract)
def subtract(a, b):
return a - b
3. 调用测试¶
现在调用 add(1, 2) 时,实际执行的是 wrapper 函数,会自动打印日志:
add(1, 2)
# 输出:
# 函数 add 开始执行,参数:(1, 2), {}
# 函数 add 执行结束,结果:3
三、装饰器的核心细节¶
1. *args 和 **kwargs¶
*args 接收位置参数(如 add(1, 2) 中的 1, 2),**kwargs 接收关键字参数(如 add(a=1, b=2) 中的 a=1, b=2)。这确保装饰器能适配任何参数的函数。
2. 保留原函数信息¶
如果直接用上述装饰器,add.__name__ 会变成 wrapper(因为 add 被替换成了 wrapper)。这时可以用 functools.wraps 保留原函数的元信息:
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
# 现在查看函数名
print(add.__name__) # 输出:add(而非 wrapper)
四、带参数的装饰器¶
如果需要给装饰器传递参数(如不同的日志前缀),可以嵌套一层函数:
def log_decorator(prefix="【日志】"): # 外层函数:接收装饰器参数
def decorator(func): # 内层函数:接收原函数
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix} 函数 {func.__name__} 开始执行")
result = func(*args, **kwargs)
print(f"{prefix} 函数 {func.__name__} 执行结束")
return result
return wrapper
return decorator # 返回内层装饰器
# 使用带参数的装饰器
@log_decorator(prefix="【调试】")
def multiply(a, b):
return a * b
multiply(3, 4)
# 输出:
# 【调试】 函数 multiply 开始执行
# 【调试】 函数 multiply 执行结束
五、装饰器的应用场景¶
装饰器非常灵活,常见场景包括:
- 日志记录:记录函数调用时间、参数、返回值。
- 性能测试:统计函数执行耗时(如 @timer_decorator)。
- 权限验证:调用函数前检查用户权限(如 @check_permission)。
- 缓存:缓存函数结果,避免重复计算(如 @cache_decorator)。
六、多个装饰器的执行顺序¶
如果多个装饰器作用于同一函数,执行顺序是从下往上(靠近函数的装饰器先执行)。例如:
@decorator2
@decorator1
def func():
pass
执行顺序是:decorator2 → decorator1 → func → decorator1 → decorator2。
总结¶
装饰器是Python的“魔法工具”,通过函数嵌套和闭包思想,让我们能优雅地给函数添加功能。核心要点:
1. 装饰器是接收函数的函数,返回新函数。
2. @ 语法糖简化了装饰器的使用。
3. *args 和 **kwargs 处理任意参数,functools.wraps 保留原函数信息。
4. 带参数的装饰器需嵌套两层函数。
从简单的日志到复杂的权限验证,装饰器让代码更简洁、可维护。现在,你可以尝试给一个自己的函数加上日志装饰器,体验它的便捷了!