总是混淆python中的赋值、浅拷贝和深拷贝的概念,写一篇笔记加强理解。
先上结论:
- 赋值:其实就是原对象多了一个引用。
- 浅拷贝(copy):只拷贝了最外层对象,内层对象还是之前的引用。
- 深拷贝(deepcopy):完全拷贝了外层和内层所有对象,可以理解为增加了一个副本。
可变对象与不可变对象
Python使用对象模型来存储数据,构造任何类型的值都是一个对象。创建新对象后,变量并不存储对象的值,而是对象引用(内存地址)。所以python中赋值等操作传递的都是引用。
- 可变对象:列表、字典
- 不可变对象:字符串、数值、元组
为了优化内存的使用,python中无论是浅拷贝还是深拷贝,对不可变对象都是无效的。拷贝之后依旧指向原来的内存地址。
赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
x = ['a', 'b', ['c', 'd', 'e']]
y = x # 赋值
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
print("--"*20)
x[0] = 'fff'
x[2].append('ggg')
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
|
以上代码的输出为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
['a', 'b', ['c', 'd', 'e']]
4780872328
[4545160840, 4544919232, 4780872200]
['a', 'b', ['c', 'd', 'e']]
4780872328
[4545160840, 4544919232, 4780872200]
----------------------------------------
['fff', 'b', ['c', 'd', 'e', 'ggg']]
4780872328
[4545027632, 4544919232, 4780872200]
['fff', 'b', ['c', 'd', 'e', 'ggg']]
4780872328
[4545027632, 4544919232, 4780872200]
|
赋值,就是原对象多了一个引用。
赋值后,y和x指向同一个内存地址,并且内层对象的地址也都是相同的。
由于x和y共用一个引用,所以对x的更改会体现在y上。
图解:
浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import copy
x = ['a', 'b', ['c', 'd', 'e']]
y = copy.copy(x) # 浅拷贝
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
print("--"*20)
x[0] = 'fff'
x[2].append('ggg')
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
|
以上代码的输出为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
['a', 'b', ['c', 'd', 'e']]
4624998472
[4389308040, 4389066432, 4624936840]
['a', 'b', ['c', 'd', 'e']]
4624999432
[4389308040, 4389066432, 4624936840]
----------------------------------------
['fff', 'b', ['c', 'd', 'e', 'ggg']]
4624998472
[4389174832, 4389066432, 4624936840]
['a', 'b', ['c', 'd', 'e', 'ggg']]
4624999432
[4389308040, 4389066432, 4624936840]
|
浅拷贝,只拷贝了最外层对象,内层对象还是之前的引用。
浅拷贝后,y和x指向同一个内存地址,并且内层对象的地址也都是相同的。
更改x的最外层元素x[0],x[0]指向了新的值,并且内存地址也和原来不同了;而y[0]并未产生改变,依旧指向原来的字符串’a’。
向x[2]添加一个元素,是更改了列表的内层元素,列表的内存地址并未产生改变,
y[2]依旧指向这个内存地址,所以x对列表的更改会在y上体现出来。
图解:
深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import copy
x = ['a', 'b', ['c', 'd', 'e']]
y = copy.deepcopy(x) # 深拷贝
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
print("--"*20)
x[0] = 'fff'
x[2].append('ggg')
print(x)
print(id(x))
print([id(temp) for temp in x])
print(y)
print(id(y))
print([id(temp) for temp in y])
|
以上代码的输出为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
['a', 'b', ['c', 'd', 'e']]
4636782856
[4401088136, 4400846528, 4636782728]
['a', 'b', ['c', 'd', 'e']]
4636783816
[4401088136, 4400846528, 4636783688]
----------------------------------------
['fff', 'b', ['c', 'd', 'e', 'ggg']]
4636782856
[4400954928, 4400846528, 4636782728]
['a', 'b', ['c', 'd', 'e']]
4636783816
[4401088136, 4400846528, 4636783688]
|
深拷贝,完全拷贝了原来的外层对象和内层对象,可以理解为增加了一个副本。
深拷贝后,y指向的地址和x已经不一样了,可见程序申请了新的内存空间存储最外层的列表。
关于列表内的元素,前两个字符串对象的地址并未改变。之前已经提到,深拷贝对不可变对象无效。第三个对象是列表,属于可变对象,所以y[3]的地址已经与x[3]不同。
深拷贝可以理解为增加了一个副本,所以对x的更改不会体现到y上。
更新x[0]的值,虽然原来x[0]和y[0]指向的是同一个地址,不过由于是深拷贝,y[0]不会改变,程序会申请新的内存空间存储x[0]的新值。
x[2]和y[2]指向两个不同地址的列表,所以对x[2]的更改并不会作用到y[2]上。
图示: