人工智能学习中深度学习之python基础之 装饰器(精讲)
学习思路
本质:装饰器的本质就是python的<语法糖>,其本质是一个函数/类。他的作用是在不修改被装饰对象源代码和调用方式的前提下,为其添加额外功能。
学习思路:
闭包函数->函数装饰器->类装饰器
1.闭包函数:
人工智能学习中深度学习之python基础之迭代器、生成器、文件处理和模块等-CSDN博客
详情的闭包函数推理请看我之前的文章当中的闭包函数和装饰器的部分内容,此处我仅简单介绍一下闭包函数的判定条件。
闭包函数本质是特殊的嵌套函数分为内函数和外函数
闭包函数的判定(两个条件同时满足):
1.内层函数引用了外层函数的变量(外层函数内部,内层函数外部的变量)。
2.外层函数的返回值是内层函数的函数名。
#简单的闭包函数def outer(x): # 外层函数,定义局部变量 xdef inner(y): # 内层函数,引用外层变量 xreturn x + yreturn inner # 外层函数返回内层函数# 调用 outer,得到闭包函数 inner,此时 x=10 被“记住”
closure = outer(10)
print(closure(5)) # 输出 15(10+5,x 仍被保留)
print(closure(3)) # 输出 13(10+3,x 始终存在)
用上述例子来解释闭包函数的原理
原理:当外层函数 outer 执行结束后,其局部变量 x 本应被销毁,但由于内层函数 inner 引用了 x,Python 会创建一个 “闭包环境” 保存 x,供 inner 后续调用时使用。
符合上述的原理且满足闭包函数的两点判定条件的嵌套函数称之为闭包函数
2.函数装饰器
1.定义:函数装饰器是用函数实现的装饰器,本质是一个 “接收函数作为参数,并返回新函数的闭包函数”。
2.核心作用:在不修改被装饰函数源代码和调用方式的前提下,为其添加额外功能(如日志、缓存)。
解释:函数装饰器的本质就是特殊的闭包函数,在闭包函数的基础上满足,外层函数的参数是一个函数作为参数,内层函数的参数接收的是被装饰的函数的参数。外层函数名称+内层函数参数 =>内层函数实现。函数装饰器本质就是在此内层函数实现的基础上添加修饰,例:统计时间,统计次数
#装饰器 def log_decorator(func): # 外层函数:接收被装饰函数 funcdef wrapper(*args, **kwargs): # 内层函数:包装逻辑 print(f"调用函数:{func.__name__}") # 额外功能 result = func(*args, **kwargs) # 调用原函数return resultreturn wrapper # 返回内层函数(闭包)#被装饰的函数 @log_decorator # 装饰糖 def add(a, b):return a + bprint(add(2, 3)) # 输出:调用函数:add → 5
解释比较拗口我们结合实例来看(请关注标红的):三个等价完成函数的实现
1.被装饰函数的函数名传递给装饰器外层函数的形参(func)
add ==> func
2.被装饰函数的参数传递给装饰器内层函数的形参(*args, **kwargs)
(a, b) ==>(*args, **kwargs)
3.被装饰函数的计算返回值return结果传递给了上面1,2的组合返回值result
return xxx ==> return result
在上述闭包函数实现了被装饰函数的基础上,进行装饰,如上图中橙色加粗的语句就是该函数装饰器的功能。
基于上述详解的完整实现和相应的输出结果如下
def decorator(func):def wrapper(*args, **kwargs): # 接收任意位置参数和关键字参数print("执行前:添加功能")result = func(*args, **kwargs) # 传递参数给被装饰函数print("执行后:添加功能")return result # 返回被装饰函数的执行结果return wrapper@decorator
def add(a, b):return a + bprint(add(1, 2)) # 调用带参数的函数'''
输出结果:
执行前:添加功能
执行后:添加功能
3
'''
在上述类装饰器的基础上,如果出现装饰器本身带参数,则需要再嵌套一层函数(三层结构)
# 第一层:接收装饰器的参数
def decorator_with_param(level):# 第二层:接收被装饰的函数def decorator(func):# 第三层:实现功能包装def wrapper(*args, **kwargs):print(f"[{level}] 执行前") # 使用装饰器的参数result = func(*args, **kwargs)print(f"[{level}] 执行后")return resultreturn wrapperreturn decorator# 使用带参数的装饰器:@装饰器名(参数)
@decorator_with_param("INFO")
def add(a, b):return a + bprint(add(1, 2))'''
输出结果:
[INFO] 执行前
[INFO] 执行后
3
'''
2.1函数装饰器保留被装饰函数的元信息
问题:使用函数装饰器后我们发现一个元信息丢失的问题,被装饰的函数(如
target)实际指向wrapper,导致其元信息(如函数名、文档字符串)被覆盖:解决办法:使用
functools.wraps装饰内层函数wrapper,它会将func的元信息复制到wrapper上。如下图中的 @functools.wraps(func) # 保留元信息#出现的情况是被装饰函数的元信息丢失def decorator(func):def wrapper():func()return wrapper@decorator def target():"""我是 target 函数的文档字符串"""passprint(target.__name__) # 输出:wrapper(原本应该是 target) print(target.__doc__) # 输出:None(原本应该是文档字符串)#解决问题后的代码如下 import functoolsdef decorator(func):@functools.wraps(func) # 保留元信息def wrapper():func()return wrapper@decorator def target():"""我是 target 函数的文档字符串"""passprint(target.__name__) # 输出:target(正确) print(target.__doc__) # 输出:我是 target 函数的文档字符串(正确)
2.2多个函数装饰器的执行顺序
本质思路:执行顺序是从上到下执行,从下到上装饰:根据栈思路理解,先进后出
#多个装饰器装饰,注意返回结果def decorator1(func):def wrapper():print("decorator1 执行前")func()print("decorator1 执行后")return wrapperdef decorator2(func):def wrapper():print("decorator2 执行前")func()print("decorator2 执行后")return wrapper# 多个装饰器:先执行 @decorator2,再执行 @decorator1
@decorator1
@decorator2
def target():print("target 执行")target()'''
执行结果:
decorator1 执行前
decorator2 执行前
target 执行
decorator2 执行后
decorator1 执行后
'''
3.类装饰器
在理解完上文中的函数装饰器的核心思路后,类装饰器本质也与函数装饰器一样,通过重写 __call__ 方法实现(__call__ 使类的实例可以像函数一样被调用)。
2个对应关系
1.函数装饰器中的外层函数参数 == 类中的__init__中的参数
2.函数装饰器中内层函数的参数 == 类中的__call__中的参数
#类装饰器实例实现class Decorator:def __init__(self, func): # 接收被装饰的函数self.func = funcdef __call__(self, *args, **kwargs): # 实现装饰逻辑print("类装饰器:执行前")result = self.func(*args, **kwargs)print("类装饰器:执行后")return result@Decorator # 等价于 target = Decorator(target)
def target():print("target 执行")target() # 调用实例,触发 __call__ 方法'''
类装饰器:执行前
target 执行
类装饰器:执行后
'''
4.装饰器常见的应用场景
1.日志记录
import time import functoolsdef log_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"函数 {func.__name__} 调用耗时:{end_time - start_time:.2f}秒")return resultreturn wrapper@log_decorator def slow_func():time.sleep(1) # 模拟耗时操作slow_func() # 输出:函数 slow_func 调用耗时:1.00秒2.** 权限校验 **:限制函数的调用权限(如登录后才能调用)
def check_login(func):@functools.wraps(func)def wrapper(*args, **kwargs):if not is_login: # 假设 is_login 是判断登录状态的变量raise PermissionError("请先登录")return func(*args, **kwargs)return wrapperis_login = False@check_login def pay():print("支付成功")pay() # 抛出异常:PermissionError: 请先登录3.** 缓存(记忆化)**:缓存函数的计算结果,避免重复计算(适用于耗时的纯函数)。
def cache_decorator(func):cache = {} # 用字典缓存结果@functools.wraps(func)def wrapper(n):if n in cache:return cache[n]result = func(n)cache[n] = resultreturn resultreturn wrapper@cache_decorator def fib(n): # 计算斐波那契数列(递归方式,耗时)if n <= 1:return nreturn fib(n-1) + fib(n-2)print(fib(100)) # 第一次计算后缓存,后续调用极快
5.总结:
| 维度 | 闭包函数 | 函数装饰器 | 类装饰器 |
|---|---|---|---|
| 本质 | 特殊的嵌套函数(内层引用外层变量) | 用闭包实现的 “函数包装器” | 用类实现的 “实例包装器”(通过 __call__) |
| 核心目的 | 保存外层变量的状态 | 为函数添加无状态 / 轻量功能 | 为函数添加有复杂状态的功能 |
| 参数处理 | 外层函数可接收参数,供内层使用 | 可通过三层嵌套接收装饰器参数 | 可通过 __init__ 接收装饰器参数 |
| 状态维护 | 只能通过外层变量维护简单状态 | 状态维护较复杂(需借助非局部变量) | 天然适合维护状态(通过实例属性) |
| 适用场景 | 简单的状态复用(如计数器基础版) | 无状态功能(日志、权限校验) | 有复杂状态功能(计数、缓存有效期) |
实战中的使用场景
1.闭包函数:当你需要一个 “记住某些变量” 的函数,且功能简单(如生成定制化函数)。
2.函数装饰器:当你需要为多个函数添加无状态或轻量状态 的通用功能(如日志、权限校验)。
3.用类装饰器的场景 :当你需要为函数添加复杂状态 (如计数、缓存 + 过期时间、多条件校验)
小结:本文是笔者学习装饰器时候的理解与感悟,用于笔者自己的理解和记忆,整理出来是为了后学者能更好的理解所谓装饰器的原理和作用,希望给大家带来帮助,(如果您能点赞关注我会更开心的)如有错误或需要修改的地方,欢迎大家私信留言,万分感谢!!!
