python入门到入土---装饰器
装饰器(Decorator)是 Python 中最强大且常用的功能之一。它属于高阶函数 和 闭包的综合应用,广泛用于 Web 开发(如 Flask、Django)、日志记录、权限验证、性能监控等场景。
本文将从基本概念讲起,逐步介绍装饰器的实现机制与常见应用场景,并配合实际代码示例进行讲解。
一、什么是装饰器?
装饰器的本质就是一个函数,它接受一个函数作为参数,并返回一个新的函数。
它的作用是:在不修改原函数代码的前提下,增强或修改函数的行为。
二、装饰器的基本语法
1. 手动调用装饰器(不使用 @)
def my_decorator(func):def wrapper():print("装饰器前处理")func()print("装饰器后处理")return wrapperdef say_hello():print("Hello, World!")# 手动调用装饰器
new_say_hello = my_decorator(say_hello)
new_say_hello()# 输出:
装饰器前处理
Hello, World!
装饰器后处理
2. 使用 `@` 语法糖(更简洁)
def my_decorator(func):def wrapper():print("装饰器前处理")func()print("装饰器后处理")return wrapper@my_decorator
def say_hello():print("Hello, World!")say_hello()
三、带参数的函数如何装饰?
1、如果原函数本身有参数怎么办?我们可以在 `wrapper` 中添加参数。
def my_decorator(func):def wrapper(name):print("装饰器前处理")func(name)print("装饰器后处理")return wrapper@my_decorator
def greet(name):print(f"Hello, {name}!")greet("Alice")#输出
装饰器前处理
Hello, Alice!
装饰器后处理
2、如果想让装饰器适用于任意参数,可以使用 `*args` 和 `**kwargs`:
def my_decorator(func):def wrapper(*args, **kwargs):print("装饰器前处理")func(*args, **kwargs)print("装饰器后处理")return wrapper@my_decorator
def add(a, b):print(f"{a} + {b} = {a + b}")add(3, 5)
3、拓展 *args和**kwargs介绍
*args用于传递不定数量的位置参数,它会将传递给函数的位置参数收集到一个元组中。例如
def my_func(*args):for arg in args:print(arg)# 调用函数时,可以传入任意数量的入参
my_func(1, 2, 3)
**kwargs用于传递不定数量的关键字参数,它会将传递给函数的关键字参数收集到一个字典中。例如:
def my_func(**kwargs):for key, value in kwargs.items():print(f"{key}: {value}")my_func(a=1, b=2, c=3)
在装饰器中使用 *args 和 **kwargs 是一个非常常见的做法,这样可以确保装饰器对各种函数都通用。
四、带参数的装饰器(装饰器的装饰器)
有时我们希望装饰器本身也能接受参数。这就需要嵌套一层函数。
def repeat(num_times):def decorator(func):@wraps(func)def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(3)
def hello(name):print(f"Hello, {name}!")hello("John")# 输出
Hello, John!
Hello, John!
Hello, John!# 解析
这是一个带参数的装饰器,它的结构可以分为三层:最外层函数 repeat(num_times):
接收一个参数 num_times,表示重复执行多少次。
返回内部的装饰器函数 decorator(func)。中间层 decorator(func):
接收一个函数 func,也就是要装饰的目标函数(例如 hello)。
返回包装函数 wrapper(*args, **kwargs)。最内层 wrapper(*args, **kwargs):
接收任意的位置参数和关键字参数。
在其中循环调用目标函数 func,次数由 num_times 决定。
最终返回最后一次调用的结果。
五、使用 `functools.wraps` 保留元信息
1、 元信息
在 Python 中,每一个函数对象都有自己的元信息属性,例如:
- func.__name__: 函数的名字
- func.__doc__: 函数的文档注释(docstring)
- func.__module__: 函数所属模块
- func.__annotations__: 参数和返回值的注解
2、装饰器会遮盖原函数的 元数据信息
def log(func):def wrapper(*args, **kwargs):print(f"[LOG] 函数 {func.__name__} 正在执行")result = func(*args, **kwargs)print(f"[LOG] 函数 {func.__name__} 执行完成")return resultreturn wrapper@log
def say_hi():"""打招呼函数"""print("Hi!")print(say_hi.__name__) # wrapper
print(say_hi.__doc__) #None--- 装饰器返回的是wrapper,@log 应用到 say_hi 时,其实 say_hi 变成了 wrapper 的引用,而不是原来的 say_hi
3、如何保留元信息
Python 提供了一个标准库工具 functools.wraps,它能自动将装饰器中的 wrapper 函数“伪装”成原函数,从而保留其元信息。
from functools import wrapsdef log(func):@wraps(func)def wrapper(*args, **kwargs):print(f"[LOG] 函数 {func.__name__} 正在执行")result = func(*args, **kwargs)print(f"[LOG] 函数 {func.__name__} 执行完成")return resultreturn wrapper@log
def say_hi():"""打招呼函数"""print("Hi!")print(say_hi.__name__) # 输出: say_hi
print(say_hi.__doc__) # 输出: 打招呼函数
六、装饰器的实际应用场景
1. 日志记录
def log(func):@wraps(func)def wrapper(*args, **kwargs):print(f"[LOG] 函数 {func.__name__} 正在执行")result = func(*args, **kwargs)print(f"[LOG] 函数 {func.__name__} 执行完成")return resultreturn wrapper@log
def divide(a, b):return a / b
2. 访问权限验证
from functools import wraps# 模拟当前登录用户的角色
current_user_role = 'admin' # 可以设置为 'user' 测试无权限情况def permission_required(role):def decorator(func):@wraps(func)def wrapper(*args, **kwargs):if current_user_role == role:print(f"[权限验证通过] 欢迎, {role.upper()}!")return func(*args, **kwargs)else:raise PermissionError(f"[权限不足] 当前用户无权访问此功能,所需角色: {role}")return wrapperreturn decorator# 使用示例@permission_required('admin')
def delete_database():"""管理员专用功能: 删除数据库"""print("正在删除数据库...")@permission_required('user')
def read_data():"""普通用户功能: 查看数据"""print("正在查看数据...")# 测试 admin 用户
try:delete_database()
except PermissionError as e:print(e)#输出
[权限验证通过] 欢迎, ADMIN!
正在删除数据库...# 测试 user 用户
current_user_role = 'user'
try:delete_database()
except PermissionError as e:print(e)#输出
[权限不足] 当前用户无权访问此功能,所需角色: admin
[权限验证通过] 欢迎, USER!
3. 性能监控
import time
from functools import wrapsdef timer(func):@wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"{func.__name__} 执行耗时:{end - start:.6f} 秒")return resultreturn wrapper@timer
def slow_function():time.sleep(1)slow_function()
七、多层装饰器叠加(多个装饰器)
装饰器可以像洋葱一样层层包裹,写法如下:
而装饰器的执行顺序,是从下往上执行的
@decorator1
@decorator2
def hello():pass等价于def hello():passhello = decorator1(decorator2(hello))
八、总结:为什么使用装饰器?
优点 | 说明 |
---|---|
代码解耦 | 不修改原函数即可添加额外功能 |
高复用性 | 同一个装饰器可用于多个函数 |
可组合性强 | 多个装饰器可以组合使用 |
支持参数 | 装饰器本身也可以接收参数 |
增强可读性和可维护性 | 提升代码结构清晰度 |
拓展
装饰器是 Python 面向切面编程(AOP)思想的重要体现,是构建高质量程序的关键工具之一
特性 | Python 装饰器 | Java AOP |
---|---|---|
实现方式 | 原生语法(@decorator ) | 依赖框架(如 AspectJ、Spring AOP) |
应用范围 | 单个函数或类 | 类、包、甚至整个应用 |
执行时机 | 运行时 | 编译期、类加载期或运行时(取决于实现) |
复杂度 | 简单灵活 | 较复杂,需要学习框架 |
适用场景 | 轻量级、函数式编程 | 企业级、模块化、大型项目 |
是否侵入原代码 | 不侵入(通过包装) | 通常不侵入(通过代理模式) |
- Python 装饰器就像是你穿了一件外套,别人看不出你原本的样子,但你知道里面是你。
- Java AOP 更像是你被替换成了一架机器人的外观,但内部还是你,只是动作都被替换了。