深拷贝与浅拷贝的区别

图解Python深拷贝和浅拷贝

直接赋值

首先,如果我们不进行拷贝,而是直接赋值,很有可能会出现意料之外的结果。比如 a 是一个列表,b=a,那么修改a的同时,b也会同样被修改,因为Python对象的赋值都是进行引用(内存地址)传递的,实际上 a 和 b 指向的都是同一个对象。

>>> a = [1,2,3]
>>> b = a
>>> a[2] = 4
>>> a
[1, 2, 4]
>>> b
[1, 2, 4]
>>> a is b
True

浅拷贝

为了避免这种情况发生,我们可以使用浅拷贝:

>>> a = [1,2,3]
>>> import copy
>>> b = copy.copy(a)
>>> b is a
False
>>> a[0] is b[0]
True

浅拷贝会创建一个新的对象,然后把生成的新对象赋值给新变量。注意这句话的意思,上面这个例子中1,2,3这三个int型对象并没有创建新的,新的对象是指 copy 创建了一个新的列表对象,这样 a 和 b 这两个变量指向的列表对象就不是同一个,但和两个列表对象里面的元素依然是按引用传递的,所以a列表中的对象1和b列表中的对象1是同一个。 但是这时修改a列表的不可变对象,b列表不会受到影响:

>>> a[0] = 4
>>> a
[4, 2, 3]
>>> b
[1, 2, 3]

由于浅拷贝时,对于对象中的元素,浅拷贝只会使用原始元素的引用(内存地址),所以如果对象中的元素是可变对象,浅拷贝就没辙了。比方说列表中包含一个列表,这时改动a,浅拷贝的b依然可能受影响:

>>> a = [1,2,[3,]]
>>> b = copy.copy(a)
>>> a is b
False
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3, 4]]

可以产生浅拷贝的操作有以下几种:

  • 使用切片[:]操作
  • 使用工厂函数(list/dir/set)
工厂函数看上去像函数,实质上是类,调用时实际上是生成了该类型的一个实例,就像工厂生产货物一样.
  • 使用 copy 模块中的 copy() 函数

深拷贝

对于这个问题,又引入了深拷贝机制,这时不仅创建了新的对象,连对象中的元素都是新的,深拷贝都会重新生成一份,而不是简单的使用原始元素的引用(内存地址)。注意了,对象中的元素,不可变对象还是使用引用,因为没有重新生成的必要,变量改动时会自动生成另一个不可变对象,然后改变引用的地址。但可变对象的内容是可变的,改动后不会产生新的对象,也不会改变引用地址,所以需要重新生成。

>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a is b
False
>>> a[0] is b[0]
True
>>> a[2] is b[2]
False

这时再改变a中的元素对b就完全没有影响了:

>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3]]

特殊情况

  • 对于非容器类型(如数字、字符串、和其他'原子'类型的对象)是没有拷贝这个说法的。
>>> a = 'hello'
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True

对于这种类型的对象,无论是浅拷贝还是深拷贝都不会创建新的对象。

  • 如果元祖变量只包含原子类型对象,则不能深拷贝:
原子类型指所有的数值类型以及字符串
>>> a=(1,2,3)
>>> a = (1,2,3)
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True

元组本身是不可变对象,如果元组里的元素也是不可变对象,就没有进行拷贝的必要了。实测如果元组里面的元素是只包含原子类型对象的元组,则也属于这个范畴。

>>> a = (1,2,(3,))
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is c
True
>>> a is b
True

总结

  • Python中对象的赋值都是进行对象引用(内存地址)传递
  • 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
  • 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
  • 非容器类型(如数字、字符串、和其他'原子'类型的对象)不存在拷贝。
  • 只包含原子类型对象的元组变量不存在拷贝。