当前位置: 首页 > news >正文

人工智能学习中深度学习之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.用类装饰器的场景 当你需要为函数添加复杂状态 (如计数、缓存 + 过期时间、多条件校验)

小结:本文是笔者学习装饰器时候的理解与感悟,用于笔者自己的理解和记忆,整理出来是为了后学者能更好的理解所谓装饰器的原理和作用,希望给大家带来帮助,(如果您能点赞关注我会更开心的)如有错误或需要修改的地方,欢迎大家私信留言,万分感谢!!!

http://www.dtcms.com/a/573089.html

相关文章:

  • KP2203LGA宽电压输出PWM驱动控制器芯片典型应用
  • 关闭 macOS 屏幕捕捉 在录制或截图时显示的「录制指示浮窗」。
  • 技术变革:为何C#与.NET是未来的开发方向
  • 借助Redis实现Token黑名单机制
  • 缓存机制:Redis集成
  • 做网站要先申请域名吗搭建网站的英语
  • 优思学院|什么是5M1E?它是如何影响产品质量的?
  • 技术赋能生态保护:无人机RTMP推流平台EasyDSS在野生动植物监测中的应用实践
  • 大模型应用技术之提示词工程面试题(二)
  • 对比传统方法和深度学习方法在MATLAB视觉检测中的优缺点
  • 从容器化到自动化:Vue3 项目 Docker 部署与 GitLab CI/CD 集成 Harbor 全流程
  • 企业网站Wap在线生成格力网站建设首页
  • Hibernate 速览指南
  • 凡科网站建设步骤企业年金查询
  • AssertJ,让断言更流畅的Java测试库!
  • C++:大型语言模型与智能系统底座的隐形引擎
  • 你好,未来:零基础看懂大语言模型
  • Spring Boot 多线程文件复制服务(支持大文件流式/NIO复制、失败重试、覆盖写入)
  • 当视觉语言模型接收到相互矛盾的信息时,它会相信哪个信号?
  • 软考中级习题与解答——UML中主要关系
  • 公司做网站属于什么费用wordpress 加入页面
  • 以太网帧格式、IP数据报头部、TCP头部、UDP头部
  • CRMEB标准版小票打印功能详解
  • Java开发性能优化
  • 传输层最重要的两种协议——UDP与TCP的区别(TCP可靠但是慢,UDP不可靠但是快)
  • list(带头双向循环链表)
  • Linux的例行工作
  • Java List全面解析:从入门到精通
  • 英文网站如何做关键词怎样免费注册自己网站的域名
  • Win11卸载重装oracle 11g数据库