这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
python中赋值
赋值只是复制了新对象的引用,而不会开辟新的内存空间,其并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当一个标签被改变的时候,数据块就会发生变化,另外一个标签也会随之变化。
例子:
a = [9,2,3,4,5]
b = a
a.sort()
print(a,b) # a:[2,3,4,5,9] b:[2,3,4,5,9]
结合上面的代码,我们可以发现,对a进行排序后,b也发生了变化,这是因为赋值引用是指向某个对象,而不是新建一个独立对象,那么如何才能获得一个和a列表一样的独立对象呢?这就需要浅拷贝操作了。
python浅拷贝
浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。其具体由三种形式:
- 切片操作
- 工厂函数
- copy模块的copy函数
a = [9,3,2,4,5]
# 切片操作
b = a[::-1]
# 工厂函数
c = list(a)
# copy函数
import copy
d = copy.copy(a)
a.sort()
print(a,b) # a:[2, 3, 4, 5, 9] b、c、d:[9, 3, 2, 4, 5]
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,在list中有一个嵌套的list[],如果我们修改了它,情况就不一样了。
浅拷贝需要分成两种情况进行讨论:
-
当浅拷贝复制的对象是不可变对象(字符串、元组、数值类型)的时候其结果和赋值的结果一致。
-
当浅拷贝复制的对象是可变对象(如列表、字典等)那么又要分情况而言:
第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。
python深拷贝
所谓深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。
上面浅拷贝中当元对象中的元素不可变,那倒无所谓;但如果元素可变,浅拷贝通常会出现一些问题,例如:
list1 = [[1, 2], (30, 40)]
list2 = list(list1)
list1.append(100)
print("list1:",list1) # list1: [[1, 2], (30, 40), 100]
print("list2:",list2) # list2: [[1, 2], (30, 40)]
list1[0].append(3)
print("list1:",list1) # list1: [[1, 2,3], (30, 40), 100]
print("list2:",list2) # list2: [[1, 2,3], (30, 40)]
我们可以看见对浅拷贝内部的可变对象进行修改的时候浅拷贝对象内的数值也发生了变化,这个时候如果需要完全的复制一个对象就需要深拷贝操作来实现了。
不过,深度拷贝也不是完美的,往往也会带来一系列问题。如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环,例如:
import copy
list1 = [1]
list1.append(list1)
print(list1) # [1,[...]]
list2 = copy.deepcopy(list1)
print(list2) # [1,[...]]
列表 x 中有指向自身的引用,因此 x 是一个无限嵌套的列表。但是由于深拷贝函数deepcopy中会维护一个字典,记录已经拷贝的对象和ID,如果字典中已经存储了要拷贝的对象,则直接返回。