跳转至

P2-8.7 补充学习:区分引用与拷贝

Section ID: P2-8.7 Version: v2026.07.23

在 P2-8.2 中,我们已经看到,把一个列表赋给另一个名字,并不会自动生成新的副本。很多读者在这里会立刻混淆。

这只是同一个值换了一个名字吗?
还是说又新建了一个相似的值?

这里提供一段基础补充说明,用来区分 referenceshallow copydeep 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):通过其中一个名字追加值后,用两个名字看到的结果都会一起变化。 要确认的概念:简单赋值并不保证创建新的副本。

1
2
3
4
5
6
7
8
# 这个例子用来确认引用和复制在列表等可变值上会产生什么差异。
scores = [82, 75, 91]
other_scores = scores

other_scores.append(68)

print(scores)
print(other_scores)

输出示例如下。

[82, 75, 91, 68]
[82, 75, 91, 68]

这里 scoresother_scores 指向的是同一个列表。所以你通过一个名字追加值时,通过另一个名字看也会出现同样的变化。

这里先这样记住。

  • a = b 并不自动保证“新副本已经生成”。
  • 可变值(mutable object)可以被多个名字同时看到。

浅拷贝只重新创建外层

浅拷贝(shallow copy)会重新创建外层容器,但里面的值仍可能继续引用原来的对象。

问题场景:你想看看“只复制外层列表”的浅拷贝和原对象有什么不同。 输入(input):列表 scores,以及通过 scores.copy() 创建的 copied_scores。 期望输出(output):只有 copied_scores 被追加了值,而原对象 scores 保持原样。 要确认的概念:因为浅拷贝重新创建了外层容器,所以顶层的新增和删除可以分离开。

1
2
3
4
5
6
7
8
# 这个例子用来确认引用和复制在列表等可变值上会产生什么差异。
scores = [82, 75, 91]
copied_scores = scores.copy()

copied_scores.append(68)

print(scores)
print(copied_scores)

输出示例如下。

[82, 75, 91]
[82, 75, 91, 68]

在这个例子里,外层列表本身已经重新创建,所以 append() 的结果不会直接反映到原对象上。

但如果理解到这里就停下,还不算真正理解了浅拷贝。因为一旦里面还有一层列表,情况就会不同。

在嵌套列表里,浅拷贝很容易让人误解

现在来看“列表里面还有列表”的情况。

问题场景:你想直接确认,为什么在嵌套列表里浅拷贝会让人混淆。 输入(input):嵌套列表 matrix 和它的浅拷贝 shallow。 期望输出(output):当你修改内部列表时,原对象和浅拷贝会一起变化。 要确认的概念:浅拷贝未必会重新创建内部嵌套对象。

1
2
3
4
5
6
7
8
# 这个例子用来确认引用和复制在列表等可变值上会产生什么差异。
matrix = [[1, 2], [3, 4]]
shallow = matrix.copy()

shallow[0][0] = 99

print(matrix)
print(shallow)

输出示例如下。

[[99, 2], [3, 4]]
[[99, 2], [3, 4]]

为什么会这样?

  • 外层列表 matrix 的确被重新复制了。
  • 但里面的 [1, 2][3, 4] 这些内部列表,仍可能继续共享。

也就是说,可以把浅拷贝理解成这样一个比喻:只是重新做了外面的盒子,但盒子里的小盒子可能还是原来的。

深拷贝连内部也会重新创建

深拷贝(deep copy)会把嵌套的内部值也重新复制。

问题场景:你想确认深拷贝是否也把内部嵌套值分离开了。 输入(input):通过 copy.deepcopy(matrix) 创建的 deep。 期望输出(output):即使修改 deep,原对象 matrix 也保持不变。 要确认的概念:深拷贝会把嵌套内部结构也重新创建,因此与原对象分离。

# 这个例子用来确认引用和复制在列表等可变值上会产生什么差异。
import copy

matrix = [[1, 2], [3, 4]]
deep = copy.deepcopy(matrix)

deep[0][0] = 99

print(matrix)
print(deep)

输出示例如下。

[[1, 2], [3, 4]]
[[99, 2], [3, 4]]

现在内部列表也已经重新创建,所以修改 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 的原因。在数据预处理中,保留原对象往往很重要,因此应该先确认:我现在修改的对象,真的已经和原对象分离了吗?

可检查的结果就是:当你修改一边时,用另一个名字看到的值是否也一起变化。如果一边一改,另一边马上也变,那就很可能两个名字都在看同一个对象;如果是嵌套结构,而你只复制了外层,就应该怀疑是浅拷贝。

练习与示例

看下面这段代码,先猜每种情况分别属于 assignmentshallow copydeep copy 中的哪一种。

问题场景:你想把 assignment、浅拷贝、深拷贝一次性放在一起比较,用肉眼直接看出差异。 输入(input):嵌套列表 base,以及 case_acase_bcase_c。 期望输出(output):A 和 B 中原对象会一起变化,而 C 中原对象保持不变。 要确认的概念:在嵌套结构中,assignment 和浅拷贝仍可能共享原对象,而深拷贝会切断这种共享。

# 这个例子用来确认引用和复制在列表等可变值上会产生什么差异。
import copy

base = [[10, 20], [30, 40]]

case_a = base
case_b = base.copy()
case_c = copy.deepcopy(base)

case_a[0][0] = -1
print("A:", base, case_a)

base = [[10, 20], [30, 40]]
case_b = base.copy()
case_b[0][0] = -1
print("B:", base, case_b)

base = [[10, 20], [30, 40]]
case_c = copy.deepcopy(base)
case_c[0][0] = -1
print("C:", base, case_c)

输出示例如下。

1
2
3
A: [[-1, 20], [30, 40]] [[-1, 20], [30, 40]]
B: [[-1, 20], [30, 40]] [[-1, 20], [30, 40]]
C: [[10, 20], [30, 40]] [[-1, 20], [30, 40]]

这里的关键是 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 定义,以及嵌套对象复制差异的核心依据。