Python中copy()与deepcopy()的区别详解

宋时安
宋时安
发布时间:2026-10-13| 1人浏览过
原创

本文深入解析Python中copy()与deepcopy()的核心区别。通过变量引用机制、标签比喻及代码示例,阐明浅拷贝仅复制顶层对象而共享子对象引用,深拷贝则递归创建独立副本。掌握两者差异有助于避免修改嵌套数据时的意外副作用,提升代码安全性。

前言

要准确区分copy()与deepcopy(),必须首先理解Python对数据存储的基本机制。

所谓深复制,是指将原对象完全重新复制一遍,生成一个独立的新个体。因此,修改原始被复制对象时,不会影响已经复制出来的新对象。

浅复制则不会创建一个完全独立的新对象,它只是给原有的数据块添加了一个新的标签。因此,当其中一个标签指向的数据发生变化时,数据块随之改变,另一个标签也会反映出这一变化。

import copy
 origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
cop1 = copy.copy(origin)
cop2 = copy.deepcopy(origin)
cop1 == cop2
------>True
 cop1 is cop2
------>False 
#cop1 和 cop2 看上去相同,但已不再是同一个object
 origin[2][0] = "hey!" 
 origin
------>[1, 2, ['hey!', 4]]
 cop1
------>[1, 2, ['hey!', 4]]
 cop2
------>[1, 2, [3, 4]]

观察代码结果可知,cop1(即使用copy生成的副本)随着origin的修改而改变;而cop2(即使用deepcopy生成的副本)保持不变。

Python存储方式

Python存储变量的方式与其他面向对象编程语言(OOP)不同。它并非将值直接赋给变量,而是为变量建立指向具体值的引用(reference)。

在Python中执行a = something时,应理解为给something这个对象贴上了一个名为a的标签。若再次对a赋值,则相当于将a标签从原来的对象上取下,贴到新的对象上,从而建立新的引用关系。这一机制解释了Python中一些看似诡异的现象:

>> a = [1, 2, 3]
>>> b = a
>>> a = [4, 5, 6] //赋新的值给 a
>>> a
[4, 5, 6]
>>> b
[1, 2, 3]
# a 的值改变后,b 并没有随着 a 变

>>> a = [1, 2, 3]
>>> b = a
>>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
# a 的值改变后,b 随着 a 变了

上述两段代码中,变量a的值均发生了变化。区别在于:第一段代码中,a被直接赋予了新值(从[1, 2, 3]变为[4, 5, 6]);而第二段代码中,则是分别修改了列表中的每个元素。

然而,对变量b的影响却截然不同:一种情况下b的值未变,另一种情况下b的值发生了改变。如何利用上述引用理论来解释这种差异呢?

首先将[1, 2, 3]视为一个对象实体。a = [1, 2, 3]相当于给该实体贴上标签a;而b = a则是给同一实体再贴上标签b。


第一种情况:

执行a = [4, 5, 6]时,相当于将标签a从[1, 2, 3]上撕下,重新贴到[4, 5, 6]这个新对象上。

在此过程中,[1, 2, 3]这个对象实体并未消失。标签b自始至终都贴在[1, 2, 3]上,且其指向的引用未发生任何改变,因此b的值自然保持不变。

第二种情况:

执行a[0], a[1], a[2] = 4, 5, 6时,是直接修改了[1, 2, 3]这个对象实体本身,对其内部元素进行了重新赋值。

修改完成后,[1, 2, 3]这个对象本身的内容变成了[4, 5, 6]。在此过程中,标签a和b均未移动,它们依然指向该对象,因此a和b的值都变成了[4, 5, 6]。

理解了这一机制后,我们需要探讨:对一个复杂对象进行浅拷贝(shallow copy)时,具体发生了什么? 参考以下代码:

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一个object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

熟悉Docker的用户对“镜像”概念应不陌生,我们可以借用这一概念来类比copy的行为。

概念示意图如下:

对于复杂对象,copy并不会完全复制其所有子对象。所谓复杂对象的子对象,例如序列中的嵌套序列、字典中的嵌套序列等。对于这类子对象,Python会将其视为公共镜像存储,所有对该子对象的复制操作都被视为引用。因此,当其中一个引用修改了镜像内容后,其他引用使用该镜像时,看到的已是被修改后的内容。

具体来看这里的origin[2],即列表[3, 4]。根据浅拷贝的定义,cop1[2]指向的是与origin[2]相同的列表对象[3, 4]。因此,如果我们修改了这个列表,将导致origin和cop1同时发生变化。这就是为什么在执行origin[2][0] = “hey!”后,cop1也随之变为[1, 2, ['hey!', 4]]的原因。

而deepcopy的概念示意图如下:

执行deepcopy时,会将复杂对象的每一层都复制为一个独立的个体。

此时,origin[2]和cop2[2]虽然值均为[3, 4],但它们已不再是同一个列表对象,即实现了我们通常意义上的完全复制。

总结

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。