【免费下载链接】memory_graph
Teaching tool and debugging aid in context of references, mutable data types, and shallow and deep copy.
在 Python 中给一个变量赋值时,数据到底是被复制了,还是只共享了一份引用?b = a、copy.copy(a)、copy.deepcopy(a)三种"拷贝"行为各有差异,稍有不慎就会改出隐藏 Bug。开源工具memory_graph能把变量与数据之间的引用关系画成内存图,帮你一眼看清 Python 赋值、浅拷贝与深拷贝的 3 种行为,是学习 Python 数据模型(引用、可变性)的绝佳图解工具。
🚀 快速上手:两行代码画出内存图
先安装 memory_graph(需要 Graphviz):
pip install --upgrade memory_graph核心 API 只有三个函数:
mg.show(data):把数据的结构可视化出来mg.render(data, "out.png"):渲染成图片文件(也支持 pdf / svg / gv)mg.block(...):执行函数后阻塞等待回车,方便逐步观察
import memory_graph as mg a = [1, 2] b = a # b 和 a 是同一份 list! mg.show(locals()) # 画出所有局部变量的引用关系locals()会返回当前作用域所有局部变量的字典,是最常用的观察入口。下面用同一个嵌套列表,逐一图解 3 种行为。
🔍 行为一:赋值(Assignment)——什么都没拷贝
a = [4, 3, 2] b = a b.append(1) # 改 b 会同时改 a这里b = a只是让b这个名字也指向a所引用的那个 list,两个变量共享同一份数据。print看不出来问题,但内存图一目了然:a和b两根箭头指向同一个 list 节点。
mg.render(locals(), 'mutable2.png')生成的图正是 mutable.py 的运行结果——图中dict里并列着a、b,箭头汇合到同一个 list 上:
💡 验证小技巧:
id(a) == id(b)或a is b为True时,说明两者共享同一对象。
🧩 行为二:浅拷贝 copy.copy——只复制最外层
对一个嵌套列表做浅拷贝时,只有第一层是新列表,里面嵌套的子列表依然是共享的:
import copy a = [ [1, 2], ['x', 'y'] ] c1 = a # 行为一:赋值,全部共享 c2 = copy.copy(a) # 行为二:浅拷贝,只复制第一层 c3 = copy.deepcopy(a) # 行为三:深拷贝,完全不共享 mg.show(locals())生成的图中有三个 list 顶层节点:c1与a共用一个,c2有自己的顶层 list,但它的两个子列表与a的子列表是同一批节点;只有c3的整棵子树都独立了。
这个例子来自 src/copies.py,可直接运行复现。
行为三:深拷贝 copy.deepcopy——整棵树都独立
copy.deepcopy(a)会递归复制所有层级,改c3的任何一处都不会波及a。在 Marimo 这类交互式环境中还能结合调用栈一起看四种拷贝(赋值、.copy()、自定义拷贝、deepcopy)的对比:
三种行为速查表:
| 写法 | 行为 | 顶层 | 嵌套子对象 |
|---|---|---|---|
c1 = a | 赋值 | 共享 | 共享 |
c2 = copy.copy(a)(等价a.copy()、a[:]、list(a)) | 浅拷贝 | 新列表 | 共享 |
c3 = copy.deepcopy(a) | 深拷贝 | 新列表 | 全部新对象 |
🎯 进阶:自定义拷贝(Custom Copy)
有时标准三种行为都不满足需求——比如对象里digits想复制、letters想共享。这时可以自己写拷贝方法,src/custom_copy.py 给出了范例:
def custom_copy(self): """ Copies 'digits' but shares 'letters'. """ c = copy.copy(self) c.digits = copy.copy(self.digits) return c内存图中可以清楚看到:a和b两个对象各自拥有独立的digits列表,但letters指向同一个 list 节点:
🧊 易错点:不可变类型下,拷贝与赋值等价
tuple、int、str等不可变类型无法原地修改,"修改"时 Python 会自动产生新值,所以提前拷贝毫无意义。memory_graph 对此做了优化:copy.copy(a)和copy.deepcopy(a)对不可变类型都会退化为赋值,省时间也省内存:
混合了可变与不可变值的结构也一样——深拷贝会尽量少复制不可变部分,只保证每个可变值都有副本:
🐛 实战调试:在 IDE 里持续观察拷贝过程
拷贝相关的 Bug 往往在运行中才暴露。推荐做法:在调试器的watch里加入mg.render(locals(), "my_graph.pdf"),然后单步调试时 PDF 会实时更新所有局部变量的引用关系——下图就是在 Visual Studio Code 中调试拷贝行为时的效果:
另外两个高频陷阱,建议结合内存图对照记忆:
b += [300](原地改)会影响共享者a,但b = [400, 500](重新绑定名字)不会影响a- 函数传参时,参数与实参共享可变对象;给函数传
c.copy()副本才能隔离修改
更多示例可在仓库的 src/ 目录中找到,例如 src/rebind.py(重绑定)、src/function_call.py(函数调用共享)。
📌 总结:建立正确的 Python 心智模型
| 问题 | 答案 |
|---|---|
b = a拷贝了吗? | 没有,只是共享引用 |
改b会影响a吗? | 可变类型且共享时——会 |
| 要完全独立怎么办? | copy.deepcopy(a) |
| 如何直观确认谁和谁共享? | mg.show(locals())画图 |
memory_graph 通过memory_to_nodes(memory_graph/memory_to_nodes.py)把内存中的对象按id()去重建图,正是"共享"与"拷贝"最可靠的判据。遇到拿不准的数据流,画一张图,胜过十次print。
【免费下载链接】memory_graph
Teaching tool and debugging aid in context of references, mutable data types, and shallow and deep copy.
相关推荐
Java 深浅拷贝完全指南:浅拷贝、深拷贝、延迟拷贝与序列化拷贝实战解析
Java 深浅拷贝完全指南:浅拷贝、深拷贝、延迟拷贝与序列化拷贝实战解析 本文以 YCBlogs 仓库中的《Java深浅拷贝》笔记为核心,系统讲解 Java 对
教程技术博客文档CANN asc-devkit SIMD-API C-API asc_int82int32函数
asc_int82int32 废弃 产品支持情况 | 产品 | 是否支持 | | | : : | | Ascend 950PR/Ascend 950DT | √
人工智能深度学习算子库CANNAscendNetdata Windows 监控:静默安装 5 分钟,从服务验证到告警闭环
Netdata Windows 监控:静默安装 5 分钟,从服务验证到告警闭环 本文面向维护混合环境的系统管理员与 DevOps 工程师:用 Netdata W
可观测性指标监控告警异常检测
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考