深拷贝与浅拷贝
大家好!这一章节我们来深入探讨 Python 中的深拷贝和浅拷贝。你可能会问,这两者到底有什么区别?它们的应用场景是怎样的呢?比如我们有时会遇到需要复制一个复杂的对象时,如何确保不影响原始对象的情况。今天我们就来了解这些问题。让我们一步步展开。
简介
- 拷贝是指使用一个已存在一个对象,生成一个新的对象,两个对象在内存中具有独立的存储空间。
- 浅拷贝是指是创建一个新的对象时,只拷贝内容是原始对象的引用,而不是创建原始对象的副本数据。
- 深拷贝是指创建一个新的对象,并递归地复制原始对象及其所有嵌套对象的内容,而不仅仅是复制它们的引用。

在编程中,我们经常需要复制对象。有时我们只是想复制对象的引用,这叫做浅拷贝;而有时我们希望复制整个对象及其嵌套的内容,这就叫做深拷贝。简单来说:浅拷贝复制对象本身,但不复制对象中的其他对象,它们依然是原始对象的引用。深拷贝不仅复制对象本身,还递归地复制对象中的每一项内容,包括嵌套对象,确保完全独立。例如,如果你有一个列表,列表中包含了其他列表或字典,浅拷贝只是复制了外层的列表,内部的子列表依然指向原来的数据,而深拷贝则是对每一个元素进行独立的复制。
区别
- 浅拷贝不具有数据独立性,对象的
copy()方法,copy模块的copy()方法,工厂方法,切片等方式得到的都是浅拷贝对象。
- 深拷贝具有数据独立性,使用
copy模块中的deepcopy()方法实现深拷贝。
- 程序的大部分场景都使用浅拷贝。
- 浅拷贝,深拷贝特指容器类型保存的复杂结构,对于基本类型的数据,都是引用指向(不在缓存池中的字符串对象除外)。
- 类似公共排序方法
sorted()实现就可以使用深拷贝,因为该方法返回一个排序后的新列表,该列表可能在程序其它位置被修改,避免影响原列表,深拷贝更适合。
浅拷贝和深拷贝的最大区别在于它们如何处理复杂的数据结构,尤其是嵌套对象。浅拷贝通过 copy 方法、工厂方法、切片等方式实现,它只复制最外层的对象,而内部的嵌套对象依然引用原始数据。深拷贝使用 copy点deepcopy 方法,它会递归地复制对象及其内部的所有内容,从而确保所有的对象都互不影响。大部分情况下,我们使用浅拷贝就足够了,因为我们只关心复制对象的外层结构,但对于一些需要完全独立操作的数据结构,深拷贝则更为合适。
浅拷贝
import copy
# 原始数据
origin_data = [[1,2],{"name":"Tom", "chars":["A","B"]}]
# 使用对象的copy()方法得到浅拷贝对象
copy_data1 = origin_data.copy()
# 使用工厂方法获取浅拷贝对象
copy_data2 = list(origin_data)
# 使用切片方式获取浅拷贝对象
copy_data3 = origin_data[:]
# 使用 copy模块中的copy方法获取浅拷贝对象
copy_data4 = copy.copy(origin_data)
# 拷贝成功的验证,内容相同,地址不同
# 查看所有对象内容
print(origin_data)
print(copy_data1)
print(copy_data2)
print(copy_data3)
print(copy_data4)
# 查看所有对象的址,
print(id(origin_data))
print(id(copy_data1))
print(id(copy_data2))
print(id(copy_data3))
print(id(copy_data4))
# 当修改任意对象时,其它对象都会受影响
copy_data3[1]["chars"][1] = "BBB"
# 查看所有对象的数据
print(origin_data)
print(copy_data1)
print(copy_data2)
print(copy_data3)
print(copy_data4)
下面我们先来看看具体浅拷贝要怎么操作。在浅拷贝中,虽然我们获得了一个新的对象,但如果该对象包含其他对象,比如列表或字典,这些子对象仍然是原始对象的引用。这里给出了一个例子,展示了如何使用不同方式进行浅拷贝。首先,定义了一个包含两个元素的原始数据 origin data,这个数据结构包含一个列表和一个字典。接着,使用以下方式对原始数据进行浅拷贝。首先使用列表对象自带的 copy 方法来创建一个浅拷贝。然后使用 list 函数来创建一个新列表,这个新列表是浅拷贝。接下来使用切片操作来获取原始数据的浅拷贝。最后使用了 copy 模块的 copy 方法获取浅拷贝。然后,代码打印了原始数据和所有拷贝数据的内容,并通过 id 函数打印它们的内存地址。这里通过内存地址验证了浅拷贝的对象与原始数据是两个不同的对象(即内存地址不同)。但是,由于浅拷贝仅仅复制了外层结构,内层的对象(比如字典和列表)仍然指向原始数据的内存位置,因此修改拷贝对象 copy_data3 中的字典内容,会影响原始数据以及其他的浅拷贝对象,比如 copy_data1, copy_data2 和 copy_data4。最后,代码再次打印所有对象的内容,展示了由于修改 copy_data3 而导致所有浅拷贝对象内容的变化。通过这个示例,可以理解浅拷贝如何在内存中共享数据,尤其是当数据结构嵌套时,修改内层数据会影响所有拷贝对象。
深拷贝
import copy
# 原始数据
origin_data = [[1,2],{"name":"Tom", "chars":["A","B"]}]
# 使用 copy模块中的deepcopy方法获取深拷贝对象
deep_copy_data = copy.deepcopy(origin_data)
# 拷贝成功的验证,内容相同,地址不同
# 查看所有对象内容
print(origin_data)
print(deep_copy_data)
# 查看所有对象的址,
print(id(origin_data))
print(id(deep_copy_data))
# 当修改任意对象时,其它对象都不会受影响
origin_data[1]["chars"][1] = "BBB"
# 查看所有对象的数据
print(origin_data)
print(deep_copy_data)
深拷贝则不同,它通过 copy点deepcopy 方法确保不仅复制最外层的对象,还会递归地复制对象中的每个子对象。这样,无论你对拷贝后的对象进行什么修改,都不会影响到原始对象。比如在我们给出的例子中,修改了原始数据中的一个子对象,但深拷贝的副本并不会受影响,它们是完全独立的。
总结
- 深拷贝与浅拷贝的概念
- 深拷贝与浅拷贝的区别
最后我们来总结一下。浅拷贝只复制最外层对象,内部嵌套对象依然是原始对象的引用。深拷贝复制整个对象及其嵌套对象,确保对象之间完全独立。了解这些区别后,你可以根据不同的需求,选择使用浅拷贝还是深拷贝,确保代码的正确性与数据的独立性。