简单理解Python中的深拷贝与浅拷贝

I. 简介

深拷贝会递归的创建一个完全独立的对象副本,包括所有嵌套的对象,而浅拷贝只复制嵌套对象的引用,不复制嵌套对象本身。

简单来说就是两者都对原对象进行了复制,因此使用is运算符来比较新旧对象时,返回的都是False(都开辟了新的内存);两者区别在于对嵌套对象有没有进行递归的复制。浅拷贝没有给嵌套对象复制并分配新内存,用is来比较嵌套对象时返回的是True;而深拷贝对嵌套对象开辟了进行了复制并分配新内存,用is来比较嵌套对象时返回的是False。

一个例子如下,我们分别对链表的头结点执行深拷贝与浅拷贝:

# 原链表 ↓
a1 -> b1 -> c1 -> d1 -> e1
# 浅拷贝 ↓ 对于嵌套对象b1, c1, ..., 直接采用了原有引用
a2 -> b1 -> c1 -> d1 -> e1
# 深拷贝 ↓ 对于嵌套对象,同样开辟了内存空间将其复制
a2 -> b2 -> c2 -> d2 -> e2

从代码实现来讲,深拷贝可以用copy库的deepcopy方法实现;浅拷贝除了用copy库的copy方法,还有许多其他的实现途径,接下来我们将进行介绍。


II. 列表

A. 首先要注意一点,对于常用的等号赋值操作,这一操作并没有进行任何拷贝,只是创建了对现有对象的一个新引用:

arr1 = [1, 2, 3, 4]
arr2 = arr1
print(arr2 is arr1)  # True
arr2[0] = 0
print(arr1)  # [0, 2, 3, 4]

B. 对列表进行切片属于浅拷贝操作:

arr1 = [1, 2, 3, 4]
arr2 = arr1[:]
print(arr2 is arr1)  # False
arr2[0] = 0
print(arr1)  # [1, 2, 3, 4]

C. 浅拷贝并不会复制嵌套对象:

arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = arr1[:]
print(arr2 is arr1)  # False(最外层被复制)
print(arr2[-1] is arr1[-1])  # True(嵌套对象没有被复制)
arr2[-1][0] = 0 
print(arr1)  # [1, 2, 3, [0, 5, 6]](被修改)

D. 深拷贝才会复制嵌套对象:

import copy
arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = copy.deepcopy(arr1)
print(arr2 is arr1)   # False
print(arr2[-1] is arr1[-1])   # False(嵌套对象也被复制)
arr2[-1][0] = 0 
print(arr1)   # [1, 2, 3, [4, 5, 6]](未修改)

E. 使用数据类型本身的构造器仍属于浅拷贝:

arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = list(arr1)  # 使用构造器创建新对象, 属于浅拷贝
print(arr2 is arr1)   # False
print(arr2[-1] is arr1[-1])  # True
arr2[-1][0] = 0 
print(arr1)   # [1, 2, 3, [0, 5, 6]]

F. 对列表进行修改所返回的新列表也属于浅拷贝(先浅拷贝再修改):

arr1 = [1, 2, 3, [4, 5, 6]]
arr2 = arr1 + []  # 先浅拷贝再修改
print(arr2 is arr1)  # False
print(arr2[-1] is arr1[-1])  # True
arr2[-1][0] = 0 
print(arr1)  # [1, 2, 3, [0, 5, 6]]

III. 字符串

A. Python中的字符串是不可变对象。因此,如果对其进行完整切片[:],可以发现这一过程并没有对字符串本身进行修改。那么Python此时只会直接记录原字符串对象的引用,不进行任何拷贝。从设计动机的角度理解,既然本身不可修改,并且进行的切片操作也没有进行修改,那么复制的意义不大,所以干脆不进行复制:

s1 = "1234"
s2 = s1[:]
print(s2 is s1)  # True(引用的内容相同)

B. 以上结论同样适用于对字符串进行"假修改",此时也不会进行任何拷贝:

s1 = "1234"
s2 = s1 + ""
print(s2 is s1)  # True(没有进行实质修改)

C. 想要进行拷贝,那就得对字符串进行实质修改。如果切片运算改变了原字符串的内容,由于字符串是不可变的,因此只能开辟一个新的内存,来存储修改后的字符串。此时进行了拷贝过程。注意,由于字符串本身没法嵌套对象,因此这里不区分深拷贝与浅拷贝:

s1 = "1234"
s2 = s1[::-1][::-1] # 进行两次修改,翻转两次
print(s2 is s1) # False
print(s2) # 1234
s3 = s1 + "5" 
print(s3 is s1) # False

D. 使用构造方法str,也不会进行任何拷贝,只是创建了另一个指向原字符串对象的引用:

s1 = "1234"
s2 = str(s1)
print(s2 is s1)  # True

E. 使用copy或deepcopy都不能对字符串内容进行拷贝,只会新增一个引用:

import copy
s1 = "1234"
s2 = copy.copy(s1)
s3 = copy.deepcopy(s1)
print(s2 is s1)  # True
print(s3 is s1)  # True

你可能感兴趣的:(划水)