赋值与深浅拷贝

赋值

赋值操作新变量名是直接指向原变量名所对应的内存地址,无论是通过新变量名还是原变量名改变变量值内的元素,二者所对应的变量值都会改变(因为是同一个)。

l1 = ['a', 'b', 'c', [10, 20, 30]]
l2 = l1
print(id(l1), id(l2))
print(id(l1[0]), id(l2[0]))
print(id(l1[-1]), id(l2[-1]))
print(id(l1[-1][1]), id(l2[-1][1]))
l1[-1][1] += 10
print(id(l1[-1][1]), id(l2[-1][1]))
print(l1, l2)

41465280 41465280
31161072 31161072
41464448 41464448
8791074277632 8791074277632
8791074277952 8791074277952
['a', 'b', 'c', [10, 30, 30]] ['a', 'b', 'c', [10, 30, 30]]

注:所有图示仅用来表示赋值和深浅拷贝的底层逻辑,不考虑数据在内存中实际存储。

拷贝

拷贝是音译的词,是从copy这个英文单词音译过来的,copy其实就是复制一份。

在Python中,列表的内存地址存放的是元素的索引与元素内存地址的对应关系。

浅拷贝

浅拷贝指仅拷贝对象的第一层,是在内存中重新开辟了一个空间存放索引与元素内存地址的对应关系。

对于原列表内的不可变数据类型,浅拷贝指向的内存地址和原对象指向的内存地址是同一个。当原对象第一层的元素改变,指向一个新的内存地址,而浅拷贝的内存地址指向的还是原内存地址。当原对象的深层元素改变,指向一个新的内存地址,浅拷贝的内存地址指向也会跟着变。

对于列表内的可变数据类型,浅拷贝指向的内存地址和原对象指向的内存地址是也同一个。但对子列表内的元素,并没有指向列表内元素的内存地址。当子列表内的元素改变,子列表的内存地址并没有变,子列表内的元素内存地址改变。浅拷贝指向子列表的内存地址不变,原子列表内存元素地址改变,浅拷贝指向的子列表元素内存地址也一起改变。

结论:当列表内第一层不可变数据类型的元素改变,浅拷贝的元素不会改变(还是指向原内存地址)。当列表内可变数据类型的子元素改变,浅拷贝的可变数据类型内的子元素也会跟着一起改变(仅指向可变数据类型本身)。

创建浅拷贝的几种方式:

  • 列表内置方法
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.copy((l1))
  • 完全切片
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = l1[:]
  • copy模块
import copy
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.copy(l1)

浅拷贝实例:

l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = l1.copy()

print(id(l1), id(l2))
41530880 41531136  # 申请一块新的内存空间

print(id(l1[-1]), id(l2[-1]))
41530048 41530048  # 第一层的每个元素地址直接引用

l1[0] += 1
print(id(l1[0]), id(l2[0]))
8791067854528 8791067854496  # 不可变数据类型值改变,id会变。浅拷贝还是指向原内存地址

print(l1,l2)  # 第一层不可变数据类型不会跟着改变。
[2, 'b', 'c', [10, 20, 30]] [1, 'b', 'c', [10, 20, 30]]

print(id(l1[-1][1]), id(l2[-1][1]))
8791067855104 8791067855104  # 通过同一个子列表指向元素的内存地址

l1[-1][0] += 10  # 改变子列表内元素的值。

print(id(l1[-1][1]), id(l2[-1][1]))
8791067855104 8791067855104  
# 通过同一个子列表指向元素的内存地址,所以浅拷贝会跟着一起改变。

print(l1, l2)
[2, 'b', 'c', [20, 20, 30]] [1, 'b', 'c', [20, 20, 30]]

深拷贝

深拷贝指拷贝对象的每一层,每一层都是在内存中重新开辟了一个空间存放一个新列表。

对于原列表内的每一层不可变数据类型,包括原列表内的子列表内的不可变数据类型,深拷贝指向的内存地址和原列表指向的内存地址是同一个。当原对象的值改变,会指向一个新的内存地址。而深拷贝的内存地址指向的还是原内存地址。

对于列表内的可变数据类型,深拷贝会重新申请一块内存空间,存放原列表内元素索引与元素id的对应关系。

结论:当列表内每一层不可变数据类型的元素改变,深拷贝内的元素都不会改变(指向的是原内存地址)。当列表内可变数据类型(列表本身,而非列表内的不可变类型元素)改变,深拷贝还是不变(可变类型深拷贝是重新申请了内存空间)。

创建深拷贝:

要使用到copy模块。

import copy
l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.deepcopy(l1)

深拷贝实例:

l1 = [1, 'b', 'c', [10, 20, 30]]
l2 = copy.deepcopy(l1)  

print(id(l1), id(l2))  # 申请一块新的内存空间。
41334272 41334528

print(id(l1[0]),id(l2[0]))  # 不可变数据直接引用。
8791063463584 8791063463584

print(id(l1[-1]), id(l2[-1]))
41333440 41309312  # 可变数据类型申请新的内存空间。

# 不可变数据改变,深拷贝还是指向原内存地址,不会跟着改变。
l1[0] += 1
print(id(l1[0]), id(l2[0]))
8791063463616 8791063463584
print(l1,l2)
[2, 'b', 'c', [10, 20, 30]] [1, 'b', 'c', [10, 20, 30]]

print(id(l1[-1][0]), id(l2[-1][0]))
8791063463872 8791063463872  # 子列表内的不可变数据元素内存地址深拷贝也是直接引用

# 子列表内不可变数据改变,深拷贝不会跟着改变。
l1[-1][0] += 10
print(id(l1[-1][0]), id(l2[-1][0]))
8791063464192 8791063463872

print(l1, l2)
[2, 'b', 'c', [20, 20, 30]] [1, 'b', 'c', [10, 20, 30]]

总结:

浅拷贝:

第一层的元素改变,浅拷贝不会跟着改变。第二层及更深层的所有数据改变,浅拷贝会跟着改变。

深拷贝:

每一层的元素改变,深拷贝都不会跟着改变。不可变数据还是指向原内存地址,可变数据是新申请了一块内存空间。

原文地址:https://www.cnblogs.com/ChiRou/p/14023604.html