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

Hazel
Hazel
发布时间:2026-10-16| 6人浏览过
原创

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

前言

理解copy()与deepcopy()的差异,关键在于掌握Python对数据的存储机制。

首先给出核心结论:

  • 通常意义上的“复制”实际上对应深复制(Deep Copy),即生成一个完全独立的新对象。修改原始被复制对象,不会影响这个新对象。
  • 浅复制(Shallow Copy)则不同,它不会生成一个完全独立的新对象,而是为原有数据块添加一个新的引用标签。因此,当原始数据块内容发生变化时,所有指向该数据块的标签都会反映这一变化。这与通常理解的“复制”概念存在差异。

对于简单的不可变对象(如整数、字符串),使用浅拷贝和深拷贝没有区别。

然而,对于复杂的对象,例如包含嵌套列表的列表(list中套list),浅拷贝中的子列表并未从原对象中真正“独立”出来。这意味着,如果修改了原对象子列表中的某个元素,浅拷贝生成的对象也会随之改变。这与我们直觉上对“复制”的理解不同。

如果文字描述难以理解,我们可以通过代码示例来直观展示:

>>> 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

从代码运行结果可以看出,cop1(浅拷贝)随着origin的变化而变化,而cop2(深拷贝)保持不变。

看起来,deepcopy更符合我们对“复制”的直觉定义:一旦复制完成,新对象就应该是独立的。如果我们希望获得字面意义上的完全复制,直接使用deepcopy即可。

那么,为什么还会存在shallow copy这种看似“假”的copy呢?这正是值得深入探讨的地方。

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]);而第二段代码中,是分别修改了list中的每个元素。

这两种操作对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]。

理解了这一机制后,我们需要进一步探讨:对复杂对象进行浅拷贝时,具体发生了什么?

再看一段代码示例:

>>> 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]这个list。根据shallow copy的定义,在cop1[2] 指向的是同一个list [3, 4]。因此,如果我们修改了这个list,就会导致origin和cop1同时改变。这就是为什么在origin[2][0] = “hey!” 执行后,cop1也随之变成了[1, 2, ['hey!', 4]]。

deepcopy的概念示意图如下:

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

此时,origin[2]和cop2[2]虽然值都等于[3, 4],但它们已不是同一个list对象。这符合我们通常意义上的“复制”定义。

到此,关于Python中copy()与deepcopy()方法区别的介绍已完毕。如需了解更多Python copy()相关内容,可搜索本站以往文章或继续浏览后续文章。感谢大家的支持!

相关标签:

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

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 算法库。