type
status
date
slug
summary
tags
category
icon
password
📝 闭包中外层变量的内存变化详解
1、核心概念
在闭包中,内层函数捕获外层变量时:
- 可变对象(列表/字典等):修改内容时内存地址不变
- 不可变对象(整数/字符串等):重新赋值时会创建新对象
- 闭包通过
__closure__
属性保存捕获的变量
2、示例代码
3、执行结果分析
4、内存变化图示
5、关键变化分析
5.1、可变变量(列表)的内存行为
- 内存地址始终不变(示例中
0x7f9a5c015b40
)
- 内容修改在原始内存位置进行
- 闭包通过
__closure__[0]
持续引用同一对象
5.2、不可变变量(整数)的内存行为
- 每次重新赋值(count += 1)创建新对象
- 内存地址变化(0x7f9a6421b5d0 → 0x7f9a6421b5f0 → 0x7f9a6421b610)
- 闭包通过
__closure__[1]
引用最新对象
- 旧整数对象被垃圾回收(如果没有其他引用)
5.3、未声明的不可变变量(name)
- 内层函数中的赋值
name = "updated"
创建局部变量
- 外层闭包中的name保持不变
- 每次调用都创建新字符串对象(但Python会重用短字符串)
6、闭包内部结构验证
7、内存变化总结表
变量类型 | 操作 | 内存地址变化 | 对象数量变化 | 闭包引用更新 |
可变对象 | append() | 不变 | 原对象修改 | 引用不变 |
不可变对象 | 重新赋值 | 每次变化 | 创建新对象 | 引用更新 |
未声明变量 | 内层赋值 | 每次变化 | 创建新对象 | 不更新闭包 |
8、内存管理注意事项
8.1、内存泄漏风险:闭包会使所有捕获变量保持活跃状态
8.2、意外共享:多个闭包共享外层变量
8.3、解决方案:需要独立状态时,使用参数传递
📝 闭包引用
1、定义
闭包引用 = 内层函数持续持有外层函数局部变量的 cell 指针,即使外层栈帧已销毁。
2、底层结构(图示)
cell
是一个 C-level 对象,内部存 PyObject。变量值改变 ⇒ cell 内容变,但 cell 地址不变,因此闭包永远拿到“同一口袋”。
3、四种引用场景对照表
场景 | 代码片段 | 是否形成闭包引用 | 备注 |
只读 | lambda: x | ✅ | 读 cell |
修改 | nonlocal x; x += 1 | ✅ | 改 cell |
重新绑定 | x = 20 (无 nonlocal) | ❌ | 创建同名局部,遮蔽外层 |
可变对象 | lst.append(1) | ✅ | cell 指向同一 list,内容可变 |