总是混淆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]上。

图示: