干货 | 图解 Python 浅拷贝与深拷贝
部分内容翻译自:https://realpython.com/copying-python-objects/
文章目录
Python 中的赋值语句不会创建对象的拷贝,仅仅只是将名称绑定至一个对象。对于不可变对象,通常没什么差别,但是处理可变对象或可变对象的集合时,你可能需要创建这些对象的 “真实拷贝”,也就是在修改创建的拷贝时不改变原始的对象。
本文将以图文方式介绍 Python 中复制或“克隆”对象的操作。
首先介绍一下 Python 中浅拷贝与深拷贝的区别:
- 浅拷贝:浅拷贝意味着构造一个新的集合对象,然后用原始对象中找到的子对象的引用来填充它。从本质上讲,浅层的复制只有一层的深度。复制过程不会递归,因此不会创建子对象本身的副本。
- 深拷贝:深拷贝使复制过程递归。这意味着首先构造一个新的集合对象,然后递归地用在原始对象中找到的子对象的副本填充它。以这种方式复制一个对象,遍历整个对象树,以创建原始对象及其所有子对象的完全独立的克隆。
赋值与引用
在开始浅拷贝与深拷贝前,我们先来看一下 Python 中的赋值与引用。
lst = [1, 2, 3] new_list = lst
从字面上看,上述语句创建了变量
lst和
new_list,并且
lst和
new_list的赋值都为一个列表。但是,Python 的赋值语句并不会复制对象,而是会重新创建一个对象的引用。
可以看出,
lst和
new_list都引用了同一个列表。
创建浅拷贝
不少教程里都会提到,如果你有一个列表,当你想要修改列表中的值但却不想影响原始对象时,可以使用
list或
somestring[:]复制(浅拷贝)一个列表。
我们先来试一下:
lst = [1, 2, 3] new_list = list(lst) # 或 new_list = lst[:]
没错,
lst和
new_list分别指向了不同的列表。当修改
lst列表中的值时,并不会对
new_list对象产生影响。
lst[0] = 'x' print(lst) print(new_list)
['x', 2, 3] [1, 2, 3]
之所以说
list语句是浅拷贝,是因为这种修改只对一层对象有效,当列表中有子对象时,对子对象的修改将影响原始对象和浅拷贝对象。
为了解释这一说法,让我们先创建一个嵌套列表,并使用
list函数创建浅拷贝。
lst = [[1, 2, 3], [4, 5, 6]] new_list = list(lst)
这里
new_list是有着和
lst一样内容的新的独立的对象。
可以看到
lst和
new_list分别指向了不同的对象。
对第一层
lst的修改,将不会对
new_list副本造成影响。
lst.append([7, 8, 9]) print(lst) print(new_list)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] [[1, 2, 3], [4, 5, 6]]
但是,因为我们只创建了原始列表的一个浅拷贝,所以
new_list仍然包含对
lst中存储的原始子对象的引用。
也就是如上图所示,
lst和
new_list的子列表都指向了相同的对象。
子对象没有被复制,它们只是在复制的列表中被再次引用。
因此,当你修改
lst中的一个子对象时,这种修改也会反映到
new_list中—— 这是因为两个列表共享相同的子对象。这种复制只是一个浅的,一个层级的复制:
lst[0][0] = 'x' print(lst) print(new_list)
[['x', 2, 3], [4, 5, 6], [7, 8, 9]] [['x', 2, 3], [4, 5, 6]]
如果我们在第一步中创建了一个
lst的深拷贝,那么两个对象就完全独立了。这是对象的浅拷贝和深拷贝之间的实际区别。
使用 Python 标准库中的
copy模块可以创建深拷贝,这个模块为创建任意 Python 对象的浅拷贝和深拷贝提供了一个简单的接口。
创建深拷贝
这次我们使用
deepcopy()函数创建一个对象的深拷贝:
import copy lst = [[1, 2, 3], [4, 5, 6]] new_list = copy.deepcopy(lst)
从图中可以看出
lst和
new_list中的子对象指向了不同的对象,如果对
lst的子对象进行修改,将不会影响
new_list。
这一次,原始对象和复制对象都是完全独立的。如前面所说,递归克隆了
lst,包括它的所有子对象:
lst[0][0] = 'x' print(lst) print(new_list)
[['x', 2, 3], [4, 5, 6]] [[1, 2, 3], [4, 5, 6]]
copy模块中的
copy.copy()函数也可以创建对象的浅拷贝。使用
copy.copy()可以明确地表示创建浅拷贝。对于内置集合,简单地使用
list、
dict和
set等工厂函数来创建浅拷贝是更加 Pythonic 的。
复制任意 Python 对象
copy.copy()和
copy.deepcopy()函数可用于复制任意对象。以前面的列表复制示例为基础。让我们从定义一个简单的 2D 点类开始:
class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f'Point({self.x!r}, {self.y!r})'
__repr__()函数使我们可以轻松地在 Python 解释器中检查从这个类创建的对象。
接下来,我们将创建一个 Point 实例,然后使用
copy模块复制(浅拷贝)它:
a = Point(23, 42) b = copy.copy(a) print(a is b)
False
a和
b分别指向了不同的 Point 实例。因为我们的 Point 对象使用不可变类型(int)作为其坐标,所以在这种情况下,浅拷贝和深拷贝没有区别。但我马上会展开这个例子。
接下来定义另一个类来表示 2D 矩形。矩形将使用 Point 对象来表示它们的坐标:
class Rectangle: def __init__(self, topleft, bottomright): self.topleft = topleft self.bottomright = bottomright def _repr__(self): return (f'Rectangle({self.topleft!r}, {self.bottomright!r})') # 创建一个 Rectangle 实例的浅拷贝 rect = Rectangle(Point(0, 1), Point(5, 6)) shallow_rect = copy.copy(rect) print(rect) print(shallow_rect) print(rect is shallow_rect)
Rectangle(Point(0, 1), Point(5, 6)) Rectangle(Point(0, 1), Point(5, 6)) False
跟前面 list 的例子一样,
rect和
shallow_rect的子对象都有相同的引用。在对象层级中修改一个对象,将看到这个变化也反映在浅拷贝的副本中:
rect.topleft.x = 999 print(rect) print(shallow_rect)
Rectangle(Point(999, 1), Point(5, 6)) Rectangle(Point(999, 1), Point(5, 6))
接下来创建 Rectangle 的深拷贝并对其进行修改:
deep_rect = copy.deepcopy(rect) deep_rect.topleft.x = 222 print(rect) print(shallow_rect) print(deep_rect)
Rectangle(Point(999, 1), Point(5, 6)) Rectangle(Point(999, 1), Point(5, 6))Rectangle(Point(222, 1), Point(5, 6))
可以看出,深拷贝完全独立于原始对象和浅拷贝对象。
参阅 copy 模块文档 可以对复制进行进一步的研究。例如,对象可以通过定义特殊的方法
__copy__()和
__deepcopy__()来控制如何复制它们。
谨记三件事
- 创建对象的浅拷贝不会克隆子对象。因此,拷贝不会完全独立于原始对象。
- 一个对象的深拷贝会递归地克隆子对象。克隆对象完全独立于原始对象,但是创建深拷贝速度较慢。
- 可以使用
copy
模块复制任意对象(包括自定义类)。
声明:本文章已授权给微信公众号“Python 那些事”原创独家发布,转载请注明
阅读更多- 图解 Python 深拷贝和浅拷贝
- 图解Python深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解:Python中的:浅拷贝与深拷贝
- 转:图解 Python 深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解Python深拷贝和浅拷贝
- 图解Python深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解 Python 深拷贝和浅拷贝
- 图解Python深拷贝和浅拷贝
- 图解Python深拷贝和浅拷贝
- 图解python中赋值、浅拷贝、深拷贝的区别
- 图解Python深拷贝和浅拷贝
- Python 拷贝文件
- Python 拷贝对象(深拷贝deepcopy与浅拷贝copy)
- python基础系列(三)---set、collection、深浅拷贝