P2-8.7 补充学习:区分引用与拷贝¶
Section ID:
P2-8.7Version:v2026.07.23
在 P2-8.2 中,我们已经看到,把一个列表赋给另一个名字,并不会自动生成新的副本。很多读者在这里会立刻混淆。
这只是同一个值换了一个名字吗?
还是说又新建了一个相似的值?
这里提供一段基础补充说明,用来区分 reference、shallow copy、deep copy。它整理的是这样一套判断标准:列表和字典结构看到的,到底是同一个实际对象,还是分离出来的副本。
这段补充学习不会深入解释 Python 的内存模型,而是先建立在数据处理和练习代码里判断 原对象会不会一起变化 所需的最低标准。
| 术语 | 本节先要抓住的含义 |
|---|---|
| reference | 多个名字同时指向同一个对象的状态。 |
| assignment | 把名字连接到另一个值或对象上的操作,但不保证生成新副本。 |
| shallow copy | 只新建外层容器,内部值仍可能共享的复制。 |
| deep copy | 连内部嵌套值也重新创建,从而与原对象分离的复制。 |
| nested structure | 像“列表里还有列表”这样内部又包含一层集合的结构。 |
阅读标准:区分引用与拷贝¶
- 能说明两个名字可以同时指向同一个对象。
- 能把浅拷贝解释为“只重新创建外层壳”的复制。
- 能把深拷贝解释为“连内部嵌套值也重新复制”的方式。
- 能说明为什么嵌套列表里的浅拷贝,看起来会像原对象也一起变化。
- 能说明在数据预处理中,如果原对象需要保留,就应先检查复制是否真的发生。
学习背景¶
在 Python 初期学习里,经常出现的是“把一个值放进变量里再修改”的场景。但一旦开始处理列表、字典、DataFrame 这类内部装着多个值的结构,改了什么 之外,改的是哪一个对象 也会变得同样重要。
这种感觉在后面的 Pandas 和数据预处理中还会再次需要。处理表格时,是保留原对象、创建新的中间结果,还是多个名字其实都在引用同一个对象,都会改变你对结果的解释。
主要学习内容¶
本补充学习先把下面三种区分作为大框架抓住。
| 区分 | 先抓住的问题 | 当前阶段的核心感觉 |
|---|---|---|
| assignment | 是新建了一个值,还是只是给同一个对象换了个名字? | assignment 不自动等于复制 |
| shallow copy | 到哪里是新建的,到哪里还是共享的? | 只新建外层,内部仍可能共享 |
| deep copy | 是否需要一个与原对象完全分离的副本? | 连嵌套的内部值也会重新创建 |
先抓住这三种区分,后面读列表处理和数据预处理例子时,就能更快看懂 为什么原对象也跟着变了?
详细学习内容¶
两个名字可以同时指向同一个列表¶
首先必须抓住的一点是,assignment 并不总是等于 copy。
问题场景:你想确认,把一个列表赋给另一个变量后,这两个名字是否在看同一个列表。 输入(input):列表 scores,以及指向同一列表的 other_scores。 期望输出(output):通过其中一个名字追加值后,用两个名字看到的结果都会一起变化。 要确认的概念:简单赋值并不保证创建新的副本。
输出示例如下。
这里 scores 和 other_scores 指向的是同一个列表。所以你通过一个名字追加值时,通过另一个名字看也会出现同样的变化。
这里先这样记住。
a = b并不自动保证“新副本已经生成”。- 可变值(mutable object)可以被多个名字同时看到。
浅拷贝只重新创建外层¶
浅拷贝(shallow copy)会重新创建外层容器,但里面的值仍可能继续引用原来的对象。
问题场景:你想看看“只复制外层列表”的浅拷贝和原对象有什么不同。 输入(input):列表 scores,以及通过 scores.copy() 创建的 copied_scores。 期望输出(output):只有 copied_scores 被追加了值,而原对象 scores 保持原样。 要确认的概念:因为浅拷贝重新创建了外层容器,所以顶层的新增和删除可以分离开。
输出示例如下。
在这个例子里,外层列表本身已经重新创建,所以 append() 的结果不会直接反映到原对象上。
但如果理解到这里就停下,还不算真正理解了浅拷贝。因为一旦里面还有一层列表,情况就会不同。
在嵌套列表里,浅拷贝很容易让人误解¶
现在来看“列表里面还有列表”的情况。
问题场景:你想直接确认,为什么在嵌套列表里浅拷贝会让人混淆。 输入(input):嵌套列表 matrix 和它的浅拷贝 shallow。 期望输出(output):当你修改内部列表时,原对象和浅拷贝会一起变化。 要确认的概念:浅拷贝未必会重新创建内部嵌套对象。
输出示例如下。
为什么会这样?
- 外层列表
matrix的确被重新复制了。 - 但里面的
[1, 2]、[3, 4]这些内部列表,仍可能继续共享。
也就是说,可以把浅拷贝理解成这样一个比喻:只是重新做了外面的盒子,但盒子里的小盒子可能还是原来的。
深拷贝连内部也会重新创建¶
深拷贝(deep copy)会把嵌套的内部值也重新复制。
问题场景:你想确认深拷贝是否也把内部嵌套值分离开了。 输入(input):通过 copy.deepcopy(matrix) 创建的 deep。 期望输出(output):即使修改 deep,原对象 matrix 也保持不变。 要确认的概念:深拷贝会把嵌套内部结构也重新创建,因此与原对象分离。
输出示例如下。
现在内部列表也已经重新创建,所以修改 deep 不会影响 matrix。
这里按下面的标准区分。
| 方式 | 直观理解 | 在嵌套结构里要注意的点 |
|---|---|---|
| assignment | 用另一个名字看同一个对象 | 一边改动后,另一边可能会原样一起变化 |
| shallow copy | 只重新创建外层 | 内部嵌套值仍可能共享 |
| deep copy | 连内部也重新创建 | 更有利于保留原对象,但成本可能更高 |
案例与示例¶
在数据预处理中,经常希望把原始表格保留下来,同时再做一个实验用副本。
- 原始数据保留不动。
- 添加派生列的实验版本另做一份。
- 为了比较过滤结果,中间版本也保留下来。
如果没有意识到复制是否真正发生,你可能以为自己保存了原对象,实际上却是在一起修改同一个对象。
在 Pandas 里,这类问题还要更谨慎。不过本节不会深入讨论 Pandas copy() 与 view / view-like 行为的细节,而是把重点连到 Part 2 Chapter 12 的一个态度:我现在是否需要先保留原始数据集?
案例 1. 明明想单独创建预处理列表,为什么原对象也一起变了¶
假设一位学习者想保留原始分数列表,只修改实验列表。于是他写下 experiment_scores = base_scores,只改动了其中一边,后来却发现原对象也一起变了。
人通常先用的判断标准是:我新建了一个名字,所以应该已经复制了。 但对于可变结构来说,assignment 不一定意味着新副本。尤其在“列表里还有列表”的嵌套结构里,究竟只是复制了外层,还是连内部也重新创建,会显著改变结果。
这也是本补充学习要区分 reference、shallow copy、deep copy 的原因。在数据预处理中,保留原对象往往很重要,因此应该先确认:我现在修改的对象,真的已经和原对象分离了吗?
可检查的结果就是:当你修改一边时,用另一个名字看到的值是否也一起变化。如果一边一改,另一边马上也变,那就很可能两个名字都在看同一个对象;如果是嵌套结构,而你只复制了外层,就应该怀疑是浅拷贝。
练习与示例¶
看下面这段代码,先猜每种情况分别属于 assignment、shallow copy、deep copy 中的哪一种。
问题场景:你想把 assignment、浅拷贝、深拷贝一次性放在一起比较,用肉眼直接看出差异。 输入(input):嵌套列表 base,以及 case_a、case_b、case_c。 期望输出(output):A 和 B 中原对象会一起变化,而 C 中原对象保持不变。 要确认的概念:在嵌套结构中,assignment 和浅拷贝仍可能共享原对象,而深拷贝会切断这种共享。
输出示例如下。
这里的关键是 B。外层已经复制了,但内部列表还是一起变化,因此你可以直接看到浅拷贝到底只把哪一层变成了新对象。
检查清单¶
- 能说明两个名字可以同时指向同一个列表吗?
- 能用嵌套列表示例说明浅拷贝和深拷贝的差别吗?
- 能说明为什么在数据预处理中要小心是否真的发生了复制吗?
- 能按原对象是否仍被共享,区分 assignment、浅拷贝和深拷贝吗?
来源与参考资料¶
- Python Software Foundation, The Python Tutorial - More on Lists, Python 3.14.6 documentation,确认日期:2026-07-20。用于确认列表赋值、
list.copy()、切片复制和列表方法示例。 - Python Software Foundation, Standard Library -
copy, Python 3.14.6 documentation,确认日期:2026-07-20。作为区分 assignment 与 copying、shallow copy 与 deep copy 定义,以及嵌套对象复制差异的核心依据。