记一次python上关于List深浅拷贝的试错。

00 起因

最近刷题时,发现python中关于list深浅拷贝有一种奇怪的矛盾。导致调试一道题的时候绕了老大一圈,下面记录一下。

01 经过

环境:Python3.7.5

众所周知,python在初始化list的时候有很多种写法,其中有一种是

1
L = [0 for _ in range(length)]

这句代码是创建list类型、长度为length的数组L并将其初始化为全0

这是一种非常棒的方法,但是有时候为了省事,会有另外一种写法

1
L = [0] * length

这种方法可以说是非常的python了,好用而且关键是敲的字母少啊

但是刷题往往会遇到二维数组,凭借着我多年python经验,不假思索写出了

1
DL = [[0] * length1] * length2

喏,这不就创建了 length2 * length1 的二维数组了么

但是当给第一个数字,也就是DL[0][0]赋值的时候,会发现神奇的现象

1
2
3
4
5
6
7
8
9
# 初始化DL
>>> DL = [[0] * 3] * 3

# 给DL[0][0]赋值1
>>> DL[0][0] = 1

# 然后打印DL
>>> print(DL)
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]

嗯?怎么回事,我明明只初始化了DL[0][0]啊,怎么DL[1][0]DL[2][0]全变成1了

突然间回想起了大学时老师上课讲的C++数组深浅拷贝的问题,数组内存分配在堆上,引用是在栈中。深拷贝开辟新的堆空间,再进行值拷贝;浅拷贝只是对原内存的引用进行了拷贝。

python底层实现依然是C/C++,大概率也是这种情况吧。[0] * 3是对象的重复,开辟了3个int的空间,返回的只是对[0, 0, 0]的引用;而[[0] * 3] * 3]是对引用的重复,返回了3个对[0, 0, 0]的引用,而且3个引用指向同一个内存地址。因此DL[0][0]DL[1][0]DL[2][0]指向同一个int内存,对DL[0][0]的赋值,会导致DL[1][0]DL[2][0]的改变。

02 总结

创建数组、初始化数组的正确姿势:

1
2
3
4
5
6
7
8
# 创建长度length数组并初始化0
L = [0 for _ in range(length)]

# 创建长度length数组并从0递增
L1 = [_ for _ in range(length)]

# 创建大小length1 * length2的二维数组并初始化0
DL = [[0 for _ in range(length2)] for _ in range(length1)]