Python 的二维数组,你写对了吗?

这是「刷题躲坑」系列的第一篇文章,这个系列帮助大家躲掉刷题/代码中常见的坑。

怪事

在 Python 中,定义长度为 3,数值全为 0 的一维列表(也就是数组)的方式有:

a = [0, 0, 0] 
a = [0 for _ in range(3)] 
a = [0] * 3

在刷算法题是,我们常常遇到的一个场景,就是需要定义一个和输入等长的一维列表。所以,我基本都是用上述的第 3 种方式。

如:

a = [0] * len(nums)

假设 nums 的长度是 5,那么运行完上述代码以后,a 的值为 [0, 0, 0, 0, 0]

我们修改一下 a 列表:

In [1]: a[0] = 666

In [2]: a
Out[2]: [666, 0, 0, 0, 0]

可以看到,修改 a 列表的第 0 个元素并不会影响到其他位置的元素。

这是理所当然的事情。

可是,当数组变成二维的时候,怪事就发生了

按照上述定义一维数组的思路,定义一个 32 列的二维数组 b

In [1]: b = [[0] * 2] * 3

In [2]: b
Out[2]: [[0, 0], [0, 0], [0, 0]]

看起来 b 是符合要求的,没问题吧。

但是,当我们修改 b 中的元素时:

In [1]: b[0][0] = 2333

In [2]: b
Out[2]: [[2333, 0], [2333, 0], [2333, 0]]

看到了吗?我们只修改了 b[0][0],但是 b 列表中的第 0 列的所有元素,全部都被修改了!!

是不是很奇怪??

分析

遇到这种奇怪的问题时,可以用我之前分享的【代码执行可视化工具】进行分析。

这个工具的地址是:https://pythontutor.com/

先看 Python 一维列表,在内存中的可视化运行情况。

从上面的动图可以看到,修改一维数组中的某个元素,并不会影响到其他元素。

再看按照之前方式定义的 Python 二维列表,在内存中的运行情况。

我们本以为的正确的二维列表应该是下面这样。即数组有 3 行,每行都是一个独立的长度为 2 的列表。

image-20220323090051693

可实际上呢?

当我们定义 b = [[0] * 2] * 3 时,虽然看起来结果是一个 3 x 2 的二维列表,但是在内存中,数组中每行都是指向同一个长度为 2 的列表

image-20220323085618167

因此,当我们修改 b[0][0] 时,虽然只是修改了内存中的一个元素,但是由于 3 行指向的是同一个地址,因此看起来是把 3 行中的元素都修改了。

image-20220323090545574

这就是怪事发生的原因。

我们在 Python 终端中看一下 b3 行的内存地址(可以使用id()函数获取内存地址),进行验证。

In [1]: b = [[0] * 2] * 3
In [2]: for i in range(3):
    ...:     print(id(b[i]))
    ...:
140178338622720
140178338622720
140178338622720

看到 b列表的 3 行内存地址确实一样的,和我们上面的可视化结果一致。

正确写法

上面是 Python 中常见的坑,负雪在刷题的时候,也被坑过。。

我们可以这么理解:

  • [0] * 2 返回的是一个内存中的地址。
  • 定义 [[0] * 2] * 3时 , [0] * 2 只运行了一次,返回了一个地址x;二维列表中存放了 3x,即[x, x, x]

那 Python 二维数组的正确写法是什么呢?

第一种写法,你可以把所有的元素都显式的写出来,这样肯定没问题:

a = [[0, 0], [0, 0], [0, 0]]

第二种写法,我们使用 for 来定义第二个维度。是推荐的写法。

In [1]: a = [[0] * 2 for _ in range(3)]

In [2]: a
Out[2]: [[0, 0], [0, 0], [0, 0]]

In [3]: a[0][0] = 666

In [4]: a
Out[4]: [[666, 0], [0, 0], [0, 0]]

为什么使用 for 的写法可以呢?

因为这种情况下 [0] * 2 运行了 3 次,所以返回的是 3 个不同的地址x,y,z。二维列表中的存放的是 [x, y, z]

可视化结果如下:

image-20220323092713004

可以看到 3 行指向不同的地址,这样的结果就是符合预期的了。

总结

今天分享了 Python 刷题中常见的一个坑:二维列表的定义

我们通过代码运行可视化工具,分析了为什么使用 * 定义二维数组会出问题。

最后也给出了使用 for 来定义二维列表的正确写法。

了解内存知识,在编程中非常有用。

推荐新手在遇到这种问题时,使用可视化工具进行排查,非常有助于理解。

以上就是「刷题躲坑」系列的第一篇文章啦!

欢迎关注我的公众号「负雪明烛」,在编程学习路上,我们一起踩坑、躲坑。

猜你喜欢

转载自blog.csdn.net/fuxuemingzhu/article/details/123698243
今日推荐