相关文章推荐
深情的绿豆  ·  libcurl: ...·  2 月前    · 
痴情的墨镜  ·  公众号使用 ...·  1 年前    · 

直接赋值

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

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

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

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

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

>>> a = [1,2,[3,]]
>>> b = copy.copy(a)
>>> a is b
False
>>> a[2].append(4)
[1, 2, [3, 4]]
[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]
    >>> a[2] is b[2]
    False
    

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

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

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

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