详细分析Python中深浅拷贝的区别

详细分析Python中深浅拷贝的区别

简而言之:

深浅拷贝的区别关键在于拷贝的对象类型是否可变。

我们可以总结出以下三条规则:

  1. 对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝
  2. 而对于不可变对象来说,深浅拷贝都不会开辟新地址,只是建立引用关联
    ,等价于赋值
  3. 对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元
    素仍为引用关系;而深拷贝对复合对象递归应用前两条规则

背后的逻辑也很容易理解,我们可以在 Python 的官方文档里找到如下解释:

Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象。

  • 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);
  • 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

下面我们通过对不同类型的对象进行深浅拷贝来逐条说明上述规则:

不可变对象

以元组(tuple)为例:

import copy

tup1 = (991, "abc")
tup2 = copy.copy(tup1) # 浅拷贝
# tup2 = tup1 # 在这个例子里浅拷贝等价于赋值

print(id(tup1))
print(id(tup2))

print(tup1 == tup2)
print(tup1 is tup2)

# 2457279675264
# 2457279675264
# True
# True


tup2 = copy.deepcopy(tup1) # 深拷贝

print(id(tup1))
print(id(tup2))

print(tup1 == tup2)
print(tup1 is tup2)

# 1291830377344
# 1291830377344
# True
# True

我们可以看到,对于不可变对象,深拷贝还是浅拷贝都不会为我们对象建立真正的副本,tup2 和 tup1的地址完全相同,实际上引用的是同一个对象。

可变对象

以列表(list)为例: 

import copy

lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 浅拷贝

print(id(lis1))
print(id(lis2))

print(lis1 == lis2)
print(lis1 is lis2)

# 2491304912896
# 2491304912960
# True
# False


lis2 = copy.deepcopy(lis1) # 深拷贝
print(id(lis1))
print(id(lis2))

print(lis1 == lis2)
print(lis1 is lis2)

# 2841088174144
# 2841088174208
# True
# False

可以看到,对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝。

复合对象

其实上面例子中的列表同时还是一个复合对象(即包含其他对象的对象)

对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元素仍为引用关系。深拷贝对复合对象递归应用前两条规则。

import copy

tup3 = (991, "abc", [])
tup4 = copy.copy(tup3) # 浅拷贝

print(tup3 is tup4) # True
print(tup3[-1] is tup4[-1]) # True

lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.copy(lis1) # 浅拷贝

print(lis1 is lis2) # False
# 虽然 lis1 和 lis2 的地址不同,但其中的每个元素都各自指向同一个对象
print(lis1[0] is lis2[0]) # True
print(lis1[1] is lis2[1]) # True
print(lis1[2] is lis2[2]) # True
print(lis1[3] is lis2[3]) # True
print(lis1[4] is lis2[4]) # True
print(lis1[5] is lis2[5]) # True
print(lis1[6] is lis2[6]) # True
print(lis1[7] is lis2[7]) # True

可以看到对于复合对象,其最外层的逻辑和前文提到的相同,即可变对象开辟新地址,不可变对象不开辟新地址。但复合对象内的元素全部只是建立引用关联,地址相同。

而深拷贝还需确认复合对象中的所有元素是否都不可变然后在对元素递归应用前两条规则。只要复合对象本身是可变的或者其中存在可变对象,则都会完成拷贝。

import copy

lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))]
lis2 = copy.deepcopy(lis1) # 深拷贝

print(lis1 is lis2) # False,列表是可变对象,深复制后地址改变
print(lis1[0] is lis2[0]) # True,索引0是整数,不可变,地址不变
print(lis1[3] is lis2[3]) # False, 索引3是列表,可变,地址改变

tup3 = (991, "abc", [])
tup4 = copy.deepcopy(tup3) # 深拷贝
print(tup3 is tup4) # False,虽然 tup3 是不可变对象,但其内部存在可变对象,所以深复制后地址仍然改变
print(tup3[0] is tup4[0]) # True,索引 0 是整数,不可变,地址不变
print(tup3[1] is tup4[1]) # True,索引 3 是字符串,不可变,地址不变
print(tup3[2] is tup4[2]) # False,索引 3 是列表,可变,地址改变

参考:

copy — Shallow and deep copy operations — Python 3.10.7 documentation(Python3 文档)

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年3月8日 下午9:20
下一篇 2023年3月8日 下午9:23

相关推荐