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

Python闭包与闭包陷阱

1 什么是闭包

在 Python 中,闭包是一种特殊的函数,它能够记住它所在的环境(也称作上下文)。这意味着闭包能够访问定义它的作用域中的变量。闭包通常用于封装数据和提供对外部访问的接口。

在 Python 中使用闭包有以下几点好处:

  1. 保存状态:闭包可以保存外部函数的状态,以便在内部函数中使用。
  2. 简化代码:闭包可以简化代码结构,使得复杂的逻辑变得简单易懂。
  3. 模块化编程:闭包可以更好地封装代码,提高代码的可重用性。
  4. 保证函数线程安全:闭包可以保证函数的线程安全性,避免全局变量被多线程修改。

2 闭包示例代码

代码示例如下:

def outer_func(x):
    def inner_func(y):
        return x + y
    return inner_func

closure = outer_func(10)
print(closure(5)) # 15

这是一个闭包的示例代码,其中outer_func是外部函数,它返回一个内部函数inner_func。内部函数使用了外部函数的变量x,并且在被调用时使用了参数y。因此,当我们调用outer_func(10)时,它返回了一个闭包(即inner_func),它记录了x=10的值。之后,我们可以调用这个闭包,并传入参数y来计算结果。

3 什么是闭包陷阱

Python中的闭包陷阱指的是在闭包中引用了变量时,如果该变量在闭包外部被修改,则闭包内部的值也会改变。这可能会导致程序的错误或意外行为。

4 闭包陷阱代码实例

请对比以下两组代码

4.1 第一组代码实例

def closure1():
    l = []
    for i in range(3):
        def inner(i_=i):
            return i_**2
        l.append(inner)
    return l


l1 = closure1()
print([i() for i in l1])

在执行代码时,首先i的在range(3)中获取的值为0,接下来执行l.append(inner)。这里inner并没有括号,所以inner本身不会被执行,而是在l中添加了一个inner函数对象。并且inner函数的形参i_默认值为0。
在这里插入图片描述
接下来,在for循环的作用下,l又被重复添加了两次inner对象,其中i_的默认值分别为1和2。
在这里插入图片描述
执行完closure1后,我们使用列表推到式去遍历l1
列表推导式中的i()使得inner对象被执行。因为i()中未传入任何参数,所以其中的i_使用了我们定义的默认参数:0,1,2。在执行完inner函数后,这些数字变成了0,1,4。因此最终的输出即为[0,1,4] 。
以上是一段正常的非闭包代码。

4.2 第二组代码实例

def closure2():
    l = []
    for i in range(3):
        def inner():
            return i**2
        l.append(inner)
    print(inner.__closure__)
    return l


l2 = closure2()
print([i() for i in l2])

这一组代码和上面一组代码没有很大的区别,唯一的差异是,这一组代码的inner并未传入形参i_。inner中的i直接取自外部。
因此,在执行closure2中的for循环时,l中依然会被传入3个inner函数对象,唯一的区别是传入的对象没有指定形参的默认值。
在执行[i() for i in l2]这个列表推到式时,inner函数并未找到对i的赋值,因此回到外部的closure2中去寻找,并找到了i的值为3。
因此,对于这段代码,每一个inner函数对象的输出都是4。
很明显这并不是我们想要的结果,这就是一个典型的闭包陷阱。

相关文章:

  • 测试篇(三):测试用例的万能公式、对水杯和登录页面设计测试用例、测试用例的设计方法
  • 第十三届蓝桥杯省赛 Java A 组 I 题、Python A 组 I 题、Python B 组 J 题——最优清零方案(AC)
  • 阿里“云开发“小程序(uniCould)
  • 提权漏洞和域渗透历史漏洞整理
  • 传参的理解
  • 基于蜣螂算法的极限学习机(ELM)分类算法-附代码
  • 主流的操作系统(带你快速了解)
  • 六、numpy拷贝
  • STM32+python产生三角波
  • 【计算机网络(考研版)】第一站:计算机网络概述(一)
  • C++空间命名
  • 树,堆,二叉树的认识
  • 计算机存储系统
  • 返回值的理解
  • 前同事居然因为 Pycharm 的这个功能,即使离职三年也依然经常被请去喝茶~
  • [case10]使用RSQL实现端到端的动态查询
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • Debian下无root权限使用Python访问Oracle
  • jdbc就是这么简单
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • Redis中的lru算法实现
  • SQLServer之索引简介
  • TypeScript迭代器
  • vue 个人积累(使用工具,组件)
  • 初识MongoDB分片
  • 大快搜索数据爬虫技术实例安装教学篇
  • 好的网址,关于.net 4.0 ,vs 2010
  • 理清楚Vue的结构
  • 排序算法之--选择排序
  • 前端存储 - localStorage
  • 前端设计模式
  • 区块链技术特点之去中心化特性
  • 转载:[译] 内容加速黑科技趣谈
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​secrets --- 生成管理密码的安全随机数​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #{}和${}的区别是什么 -- java面试
  • #etcd#安装时出错
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (1)常见O(n^2)排序算法解析
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (二开)Flink 修改源码拓展 SQL 语法
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转)linux下的时间函数使用
  • (转载)Linux 多线程条件变量同步
  • ./configure,make,make install的作用
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET Standard 的管理策略
  • .NET 动态调用WebService + WSE + UsernameToken