Python中赋值、浅拷贝与深拷贝的区别是什么?
=)仅创建对象的引用别名,不产生任何新对象;浅拷贝(如 copy.copy() 或切片)会创建一个全新的外层容器对象,但内部引用的子对象仍与原对象共享;深拷贝(copy.deepcopy())则会递归遍历并复制容器及其内部所有层级的可变子对象,生成一份完全独立的物理副本。
一、Python 的变量模型:理解引用的基石
要彻底区分这三种操作,首先需要理解 Python 的变量绑定机制。在 Python 中,“一切皆对象”,变量本质上并不是存储具体数据的“盒子”,而是一个指向内存中对象的“指针标签”。
当执行 x = [1, 2] 时,系统在堆内存中创建了列表对象 [1, 2],并将变量名 x 绑定到该对象的内存地址。内置函数 id() 可以获取对象的唯一内存标识符,而操作符 is 用于判断两个变量是否指向同一个内存地址。
二、直接赋值(=):别名绑定的行为分析
在 Python 中,使用等号 = 并不是复制数据,而是为已有对象赋予一个新的变量别名。两个变量名在内部指向完全一致的内存物理地址。
# 赋值示例
origin = [1, [2, 3]]
alias = origin
print("origin id:", id(origin))
print("alias id: ", id(alias))
print("两者是否指向同一对象:", origin is alias) # 输出: True
# 无论修改外层还是内层,两者都会同步改变
origin[0] = 99
origin[1].append(4)
print("alias 的内容:", alias) # 输出: [99, [2, 3, 4]]
这种特性的主要用途是提高变量传递的效率。在函数调用时,Python 的参数传递机制采用“对象的引用传递”(Pass by Object Reference),其本质就是赋值操作。如果函数内部直接修改了传入的可变对象,函数外部的原始数据也会随之被篡改。
三、浅拷贝(Shallow Copy):顶层复制与子对象共享
浅拷贝会构造一个新的容器对象,然后将原对象中的所有顶层元素引用逐个插入到新容器中。如果元素是基本不可变类型(如整型、字符串),修改外层元素不会相互干扰;但如果元素本身是嵌套的可变容器(如子列表、子字典),新旧容器中存储的子对象引用依然指向同一个内存块。
常见的浅拷贝实现方式
- 使用标准库内置函数:
copy.copy(x) - 列表切片操作:
new_list = old_list[:] - 容器内置方法:
new_list = old_list.copy()或new_dict = old_dict.copy() - 类构造函数/工厂函数:
new_list = list(old_list)、new_dict = dict(old_dict)
import copy
# 浅拷贝示例
origin = [10, [20, 30]]
shallow = copy.copy(origin)
print("外层对象是否相同:", origin is shallow) # 输出: False (生成了新外层容器)
print("嵌套子列表是否相同:", origin[1] is shallow[1]) # 输出: True (共享内部子列表)
# 1. 修改原对象的顶层不可变元素
origin[0] = 99
print("shallow 外层不受影响:", shallow) # 输出: [10, [20, 30]]
# 2. 修改原对象的内部嵌套可变列表
origin[1].append(999)
print("shallow 内层被同步修改:", shallow) # 输出: [10, [20, 30, 999]]
为了直观观察修改嵌套对象时的传播过程,下方的状态演变图展示了在调用 append 后,各变量的实际变化。
四、深拷贝(Deep Copy):全层级递归隔离
深拷贝(Deep Copy)会构建一个全新容器,并递归复制原对象内部涉及的所有子对象。只有当遇到不可变元素(如整数、浮点数、纯字符串、仅包含不可变元素的元组)时,为了节省内存,Python 会复用该不可变对象的引用;对于所有可变结构,都会在内存中重建一套完全平行的对象网络。
深拷贝必须通过标准库中的 copy.deepcopy() 实现:
import copy
origin_tree = {
"name": "工程节点",
"params": [100, 200],
"children": [{"sub_id": 1}]
}
deep_clone = copy.deepcopy(origin_tree)
# 验证所有层级的独立性
print("外层字典独立:", origin_tree is deep_clone) # False
print("params 列表独立:", origin_tree["params"] is deep_clone["params"]) # False
print("子字典独立: ", origin_tree["children"][0] is deep_clone["children"][0]) # False
# 即使修改深层嵌套字典,原对象也丝毫不受干扰
deep_clone["children"][0]["sub_id"] = 999
print("原字典保持不变:", origin_tree["children"][0]["sub_id"]) # 输出: 1
a.append(a)),普通递归复制会陷入无限死循环。copy.deepcopy() 在内部维护了一个名为 memo 的字典,用于记录已经复制过的对象映射关系({id(origin): cloned_obj})。一旦检测到当前对象已在 memo 中,便直接返回已有副本,从而避免死循环。
五、开发中如何选择:决策流程图
在工程开发中,盲目使用 deepcopy 会导致明显的性能下降和内存飙升。开发者应当根据数据的嵌套深度与修改需求,选择最合理的方案。
六、核心差异全景对比与工程避坑指南
为了便于查阅和快速选型,下面对三种机制的各项核心技术指标进行汇总:
| 对比维度 | 直接赋值 ( = ) | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
|---|---|---|---|
| 外层容器地址 | 完全相同 (a is b 为 True) |
全新对象 (a is b 为 False) |
全新对象 (a is b 为 False) |
| 嵌套可变子对象 | 完全相同 | 完全相同 (共享引用) | 递归克隆 (生成新对象) |
| 修改顶层元素 | 相互影响 | 互不影响 | 互不影响 |
| 修改嵌套子元素 | 相互影响 | 相互影响 | 互不影响 |
| 常用语法 | b = a |
copy.copy(a)a[:] / a.copy() |
copy.deepcopy(a) |
| 内存与执行开销 | 极小(无内存复制) | 较小(仅复制第一层引用) | 较大(遍历对象图,深层分配) |
常见典型陷阱解析
很多开发者初学时会使用 matrix = [[0] * 3] * 3 初始化二维列表。这里的外层乘法本质上是在执行浅拷贝引用,导致外层列表的 3 个元素其实指向了同一个子列表。执行 matrix[0][0] = 1 会导致三行同时被更改。正确做法是使用列表推导式:matrix = [[0] * 3 for _ in range(3)],保证每次循环都创建独立的子列表实例。
对于不可变类型(如 tuple、str、int),执行 copy.copy(t) 并不会创建新对象,而是直接返回原对象的引用,因为不可变对象不可篡改,复用引用完全安全。但若元组中包含了可变列表(例如 t = ([], )),若要使其内部列表完全独立,仍需使用 copy.deepcopy(t)。
总结
掌握赋值、浅拷贝与深拷贝的核心在于把握对象的层级边界:
- 需要变量共享同一种状态,仅作引用传递时,使用直接赋值;
- 处理扁平结构、单层列表字典,或仅需修改顶层容器时,使用浅拷贝以兼顾性能与安全;
- 涉及树形配置、复杂嵌套字典或需要彻底隔离历史快照时,使用深拷贝确保不受下游修改干扰。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









