装饰器(Decorator)是 Python 中极具特色且强大的语法特性,它能在不修改原函数代码和调用方式的前提下,为函数添加额外功能。无论是日志记录、性能监控还是权限校验,装饰器都能优雅地解决这类 “横切关注点” 问题。本文将从基础原理到实战应用,全方位拆解 Python 装饰器,帮助新手彻底掌握这一核心技能。
一、装饰器的核心原理
要理解装饰器,首先要明确 Python 的两个核心特性:函数是一等对象、闭包。
1.1 函数是一等对象
在 Python 中,函数和整数、字符串一样,具备以下特性:
- 可以赋值给变量
- 可以作为参数传递给其他函数
- 可以作为函数的返回值
- 可以存储在数据结构(如列表、字典)中
示例代码:
python
运行
# 定义普通函数
def add(a, b):
return a + b
# 1. 函数赋值给变量
func = add
print(func(1, 2)) # 输出:3
# 2. 函数作为参数传递
def call_func(func, x, y):
return func(x, y)
print(call_func(add, 3, 4)) # 输出:7
# 3. 函数作为返回值
def wrapper():
return add
print(wrapper()(5, 6)) # 输出:11
1.2 闭包:装饰器的基础
闭包是指嵌套函数引用了外部函数的变量,且外部函数返回嵌套函数。闭包能 “记住” 外部函数的环境,为装饰器提供了核心支撑。
示例代码:
python
运行
def outer(msg):
# 嵌套函数inner引用了外部函数的msg变量
def inner():
print(f"传递的消息:{msg}")
# 外部函数返回嵌套函数(不执行)
return inner
# 创建闭包实例
func = outer("Hello Decorator")
# 执行闭包,仍能访问msg变量
func() # 输出:传递的消息:Hello Decorator
1.3 装饰器的本质
装饰器本质上是一个接收函数作为参数,并返回新函数的闭包。它的核心逻辑是:
- 定义一个装饰器函数(外层函数),接收被装饰的函数作为参数;
- 定义一个包装函数(内层函数),在其中实现额外功能,并调用原函数;
- 外层函数返回包装函数。
最基础的装饰器示例:
python
运行
# 定义装饰器函数
def my_decorator(func):
# 包装函数:添加额外功能
def wrapper():
print("===== 函数执行前的额外操作 =====")
# 调用原函数
func()
print("===== 函数执行后的额外操作 =====")
# 返回包装函数
return wrapper
# 定义原函数
@my_decorator # 语法糖,等价于 say_hello = my_decorator(say_hello)
def say_hello():
print("Hello Python!")
# 调用被装饰后的函数
say_hello()
执行结果:
plaintext
===== 函数执行前的额外操作 =====
Hello Python!
===== 函数执行后的额外操作 =====
二、装饰器的进阶用法
2.1 装饰带参数的函数
包装函数需要接收原函数的参数,并传递给原函数:
python
运行
def logger(func):
def wrapper(*args, **kwargs): # 接收任意参数
print(f"调用函数:{func.__name__},参数:{args}, {kwargs}")
# 执行原函数并获取返回值
result = func(*args, **kwargs)
print(f"函数 {func.__name__} 执行完成,返回值:{result}")
return result
return wrapper
@logger
def calculate(a, b, op="+"):
if op == "+":
return a + b
elif op == "*":
return a * b
else:
return None
# 测试
calculate(10, 20)
calculate(5, 8, op="*")
执行结果:
plaintext
调用函数:calculate,参数:(10, 20), {'op': '+'}
函数 calculate 执行完成,返回值:30
调用函数:calculate,参数:(5, 8), {'op': '*'}
函数 calculate 执行完成,返回值:40
2.2 带参数的装饰器
如果需要给装饰器本身传递参数,需要再嵌套一层函数:
python
运行
# 带参数的装饰器:控制日志级别
def logger(level="info"):
# 第一层:接收装饰器参数
def decorator(func):
# 第二层:接收被装饰函数
def wrapper(*args, **kwargs):
# 第三层:包装函数
print(f"[{level.upper()}] 调用函数:{func.__name__}")
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用带参数的装饰器
@logger(level="warning")
def delete_data(id):
print(f"删除ID为 {id} 的数据")
@logger() # 不传参数使用默认值
def query_data(name):
print(f"查询名称为 {name} 的数据")
delete_data(1001)
query_data("Python")
执行结果:
plaintext
[WARNING] 调用函数:delete_data
删除ID为 1001 的数据
[INFO] 调用函数:query_data
查询名称为 Python 的数据
2.3 保留原函数的元信息
装饰器会替换原函数的__name__、__doc__等元信息,可通过functools.wraps修复:
python
运行
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
print("添加额外功能")
return func(*args, **kwargs)
return wrapper
@my_decorator
def test():
"""test函数的文档字符串"""
pass
# 未使用wraps时,test.__name__ 是 'wrapper'
print("函数名:", test.__name__) # 输出:test
print("文档字符串:", test.__doc__) # 输出:test函数的文档字符串
三、装饰器的实战应用场景
3.1 性能监控
统计函数执行耗时,定位性能瓶颈:
python
运行
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函数 {func.__name__} 执行耗时:{end - start:.4f} 秒")
return result
return wrapper
# 测试耗时装饰器
@timer
def slow_function():
time.sleep(1.5)
print("耗时函数执行完成")
slow_function()
3.2 权限校验
在接口调用前验证用户权限:
python
运行
def check_permission(role):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 模拟用户角色
user_role = "admin" # 实际场景从请求/会话中获取
if user_role == role:
return func(*args, **kwargs)
else:
raise PermissionError("无权限执行该操作")
return wrapper
return decorator
@check_permission(role="admin")
def delete_user(user_id):
print(f"成功删除用户 {user_id}")
@check_permission(role="guest")
def view_user(user_id):
print(f"查看用户 {user_id} 信息")
# 测试
delete_user(100) # 正常执行
view_user(100) # 抛出PermissionError
3.3 缓存结果
缓存函数计算结果,避免重复计算(适用于耗时的纯函数):
python
运行
def cache(func):
# 用字典存储缓存结果
cache_data = {}
@functools.wraps(func)
def wrapper(*args):
# 以参数为键,判断是否已缓存
if args in cache_data:
print(f"使用缓存:{args}")
return cache_data[args]
# 未缓存则执行函数并存储结果
result = func(*args)
cache_data[args] = result
return result
return wrapper
@cache
def fibonacci(n):
"""计算斐波那契数(递归实现,性能差,适合缓存)"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 第一次调用:计算并缓存
print(fibonacci(10))
# 第二次调用:使用缓存
print(fibonacci(10))
四、类装饰器(扩展)
除了函数装饰器,Python 也支持类装饰器,核心是实现__call__方法:
python
运行
class CountCalls:
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} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hi(name):
print(f"Hi {name}!")
say_hi("Tom")
say_hi("Jerry")
执行结果:
plaintext
函数 say_hi 已调用 1 次
Hi Tom!
函数 say_hi 已调用 2 次
Hi Jerry!
总结
- 装饰器的核心是闭包,本质是接收函数并返回新函数的高阶函数,
@语法糖简化了装饰器的调用; - 装饰器可处理带参数的函数,也可自定义参数,使用
functools.wraps能保留原函数元信息; - 装饰器适用于日志记录、性能监控、权限校验、结果缓存等场景,是 Python “开闭原则” 的最佳实践。
掌握装饰器不仅能写出更优雅、可维护的代码,也是进阶 Python 开发的必经之路。建议从简单场景入手,逐步尝试自定义装饰器,加深对其原理的理解。