通过代码和结果来理解浅拷贝和深度拷贝,先来看看python中有那些常见的浅拷贝方法:

1.使用数据类型本身的构造器:

l1 = [1,2,3]
l2 = list(l1)

l1 == l2 #True
l1 is l2 #False

#l2就是l1的浅拷贝,set、dict与上面的浅拷贝一致


t1 = (1,2,3)
t2 = tuple(t1)

t1 == t2 #True
t1 is t2 #True

#由于tuple不可变,tuple返回一个指向相同元组的引用,与list、set、dict不一样

2.通过切片操作符‘:’完成浅拷贝:

l1 = [1, 2, 3]
l2 = l1[:]

l1 == l2 #True

l1 is l2 #False

3.python中提供的函数copy.copy(),适用于任何数据类型:

import copy
l1 = [1, 2, 3]
l2 = copy.copy(l1)

l1 == l2 #True
l1 is l2 #False

浅拷贝,是指重新分配一块内存,创建一个新对象,里面的元素是原对象中子对象的引用。如果元素可变(list、set、dict),浅拷贝会带来一些副作用。如果元素不可变(Number、String、Tuple),就没有这些副作用。

l1 = [[1, 2], (30, 40)]
l2 = list(l1)
l1.append(100)
l1[0].append(3)

l1
[[1, 2, 3], (30, 40), 100]

l2
[[1, 2, 3], (30, 40)]

l1[1] += (50, 60)
l1
[[1, 2, 3], (30, 40, 50, 60), 100]

l2
[[1, 2, 3], (30, 40)]

根据以上代码,初始化了一个列表l1,里面的元素是一个列表和一个元组;,l2是l1的浅拷贝,l2中的元素和l1指向同一个列表和元组对象。

  1. 当l1.append(100),新增元素100。这个操作不会对l2产生任何影响,因为l2和l1作为整体是两个不同的对象,并不共享内存地址。
  2. 执行l1[0].append(3),对l1的列表新增元素3。由于l2是l1的浅拷贝,l2的第一个元素和l1中的第一个元素,共同指向同一个列表,因此l2中的第一个列表也会相对应的新增元素3。l2也跟着发生改变。
  3. 操作l1[1] += (50,60),因为元祖是不可变的,这里表示对l1中的元组拼接,实际上是重新创建了一个新元组作为l1中的第二个元素,而l2中没有引用新元组,l2并不受影响。

通过以上代码,可以很清楚的看到使用浅拷贝的副作用。

总结下,如果集合中有list、set、dict(可变类型)任意一个,慎重使用浅拷贝;如果集合中元素只有Number、String、Tuple(不可变元素),使用浅拷贝,就没有副作用。

要避免这种副作用,完整的拷贝一个对象,就得使用深度拷贝。是指重新分配一块内存,创建一个新对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。

python中以copy.deepcopy()来实现对象的深度拷贝。

import copy
l1 = [[1, 2], (30, 40)]
l2 = copy.deepcopy(l1)
l1.append(100)
l1[0].append(3)

l1
[[1, 2, 3], (30, 40), 100]

l2 
[[1, 2], (30, 40)]