如何在Python中初始化二维数组?

382

我正在学习Python,并尝试使用一个二维列表,最初在每个位置上用相同的变量填充。我想到了这个:

def initialize_twodlist(foo):
    twod_list = []
    new = []
    for i in range (0, 10):
        for j in range (0, 10):
            new.append(foo)
        twod_list.append(new)
        new = []

这样可以得到期望的结果,但感觉像是一个变通方法。有没有更简单/更短/更优雅的方法来做到这一点?


11
只是一个小问题(或者对于观察者而言,可能很重要): 列表不是数组。如果您需要数组,请使用numpy。 - Arnab Datta
这个问题类似于:它讨论了Python中多维数组的初始化,而不需要循环。 - Anderson Green
@ArnabDatta 你怎么在numpy中初始化一个多维数组呢? - Anderson Green
1
@AndersonGreen http://docs.scipy.org/doc/numpy/user/basics.creation.html#arrays-creation - Arnab Datta
你可以在默认的Python中像数组一样组织数据,但它远不如NumPy数组高效或有用。特别是当你想处理大型数据集时。这里有一些文档http://docs.scipy.org/doc/numpy-1.10.1/user/basics.creation.html - jmdeamer
31个回答

572

在Python中初始化二维列表,可以使用以下方法:

t = [ [0]*3 for i in range(3)]

但不要使用 [[v]*n]*n,那是个陷阱!

>>> a = [[0]*3]*3
>>> a
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> a[0][0]=1
>>> a
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]

101
是的,我也掉进了这个陷阱。这是因为 * 复制了对象(列表)的地址。 - chinuy
24
点赞,因为这让我明白了。更清晰地说,[[0] * col for _ in range(row)] - Abhijit Sarkar
2
为什么第一维可以工作,但第二维不行?l = [0] * 3 后跟 l[0] = 1 可以很好地产生 [1, 0, 0] - ArtOfWarfare
12
找到了为什么第一维有效而第二维无效的答案。列表乘法会进行浅层复制。当你对索引进行赋值时,它会进行适当的更改,但访问不会,所以当你执行 a[x][y] = 2 时,它是在访问而不是赋值 xth 索引 - 只有 yth 访问实际上被更改。这个页面用图表帮助我解释,可能比我在这条评论中试图解释的更好:https://www.geeksforgeeks.org/python-using-2d-arrays-lists-the-right-way/ - ArtOfWarfare
1
只是使用Colab,遇到了完全相同的问题...浪费了我3个小时来调试...但你是救命恩人,谢谢你,点赞。 - Near
显示剩余6条评论

418

在Python中经常遇到的一个模式是

bar = []
for item in some_iterable:
    bar.append(SOME EXPRESSION)
这促进了列表推导式的引入,它可以将那段代码片段转换为...
bar = [SOME_EXPRESSION for item in some_iterable]

使用列表推导式可以使代码更短,有时也更易于理解。通常,你会养成识别这些模式并经常用列表推导式替换循环的习惯。

你的代码两次遵循这种模式。

twod_list = []                                       \                      
for i in range (0, 10):                               \
    new = []                  \ can be replaced        } this too
    for j in range (0, 10):    } with a list          /
        new.append(foo)       / comprehension        /
    twod_list.append(new)                           /

56
顺便说一下,“[[foo]*10 for x in xrange(10)]”可以用来简化一个生成式。问题是乘法会进行浅拷贝,所以“new = [foo] * 10”会得到一个包含相同列表的列表,而“new = [new] * 10”则会得到一个包含同一列表十次的列表。 - Scott Wolchok
9
类似地,[foo] * 10 是一个包含10个完全相同的 foo 的列表,这可能很重要,也可能不重要。 - Mike Graham
3
我们可以使用最简单的方式: wtod_list = [[0 for x in range(10)] for x in range(10)] - indi60
2
@Scott Wolchok和Mike Graham - 非常重要的一点是,使用列表相乘会复制对同一列表的引用。如果不使用append,如何实例化一个MxN矩阵? - mdude380
3
对于Mike Graham关于[foo] * 10的评论:这意味着如果你想用随机数填充一个数组(将[random.randint(1,2)] * 10计算为[1] * 10[2] * 10),那么这种方法是行不通的,因为你会得到一个全是1或2的数组,而不是一个随机数组。 - Tzu Li
显示剩余3条评论

272
你可以使用一个列表推导式:list comprehension:
x = [[foo for i in range(10)] for j in range(10)]
# x is now a 10x10 array of 'foo' (which can depend on i and j if you want)

4
如果大小相同(为10),那么就可以了,如果不是,则嵌套循环必须首先执行:[foo for j in range(range_of_j)] for i in range(range_of_i) - Dineshkumar
6
这个答案可以正常工作,但由于我们使用 i 迭代行和 j 迭代列,我认为最好将您的语法中的 ij 交换位置以便更好地理解,并将范围更改为2个不同的数字。 - DragonKnight

153

这种方法比嵌套的列表推导式更快。

[x[:] for x in [[foo] * 10] * 10]    # for immutable foo!

以下是一些关于小型和大型列表的Python3计时:

$python3 -m timeit '[x[:] for x in [[1] * 10] * 10]'
1000000 loops, best of 3: 1.55 usec per loop

$ python3 -m timeit '[[1 for i in range(10)] for j in range(10)]'
100000 loops, best of 3: 6.44 usec per loop

$ python3 -m timeit '[x[:] for x in [[1] * 1000] * 1000]'
100 loops, best of 3: 5.5 msec per loop

$ python3 -m timeit '[[1 for i in range(1000)] for j in range(1000)]'
10 loops, best of 3: 27 msec per loop

解释:

[[foo]*10]*10 创建一个包含同一对象重复10次的列表。你不能直接使用它,因为修改一个元素会导致每行相同元素的修改!

x[:] 等同于 list(X) 但更高效,因为它避免了名称查找。不管哪种方式,它都创建了每行的浅层副本,所以现在所有元素都是独立的。

所有的元素都是相同的可变的 foo 对象,所以如果 foo 是可变的,你不能使用这个方案,必须使用:

import copy
[[copy.deepcopy(foo) for x in range(10)] for y in range(10)]
假设有一个返回foo的类(或函数)Foo
[[Foo() for x in range(10)] for y in range(10)]

4
@Mike,你有没有错过粗体部分?如果foo是可变的,除非你根本不改变foo,否则这里的其他答案都不起作用。 - John La Rooy
1
使用copy.deepcopy无法正确复制任意对象。如果您有一个任意可变对象,则需要针对您的数据制定特定计划。 - Mike Graham
1
如果你在循环中非常需要速度,那么现在可能是使用Cython、weave或类似工具的时候了... - james.haggerty
1
@JohnLaRooy 我认为你交换了 xy。应该是 [[copy.deepcopy(foo) for y in range(10)] for x in range(10)] - user3085931
1
@Nils [foo]*10 不会创建 10 个不同的对象 - 但是在 foo 是不可变的情况下很容易忽略这种差异,比如 intstr - John La Rooy
显示剩余6条评论

75

在 Python 中初始化一个二维数组的方法:

a = [[0 for x in range(columns)] for y in range(rows)]

6
要将所有值初始化为0,只需使用a = [[0 for x in range(columns)] for y in range(rows)]] - ZX9
[[0 for x in range(cols)] for y in range(rows)] is slow, use [ [0]*cols for _ in range(rows)] - Pegasus

39
[[foo for x in xrange(10)] for y in xrange(10)]

1
在Python3.5中,xrange()已被移除。 - Miae Kim
1
为什么这个不起作用:[0 * col] * row。当我修改某个元素时,它会在其他地方复制。但我不明白为什么? - code muncher
因为它与问题中的代码完全相同。 - Ignacio Vazquez-Abrams
4
[[0] * col] * row 之所以无法得到你想要的结果,是因为 Python 在用这种方式初始化二维列表时,并不会创建每一行的独立副本。相反,它会使用指向一个 [0]*col 的相同副本来初始化外层列表。因此,对其中一行所做的任何更改都会反映在其余行中,因为它们实际上都指向内存中相同的数据。 - Addie
只是一个想法,但是所有这些列表不都适合进行附加吗?也就是说,如果我想要一个空的二维列表,其维度为3 * 6,并且想要附加到index [0] [0],[1] [0],[2] [0]等以填充所有18个元素,那么这些答案都不会起作用,对吧? - mLstudent33

25
通常当你需要多维数组时,你不需要一个列表的列表,而是需要一个 numpy 数组或者可能是字典。
例如,使用 numpy,你可以这样做:
import numpy
a = numpy.empty((10, 10))
a.fill(foo)

5
虽然 numpy 很棒,但我认为对于初学者来说可能有些过于复杂了。 - Esteban Küber
3
NumPy提供了一种多维数组类型。使用列表构建一个好的多维数组是可行的,但对于初学者来说比使用NumPy更加困难且不太实用。嵌套列表在某些应用中非常出色,但通常不是想要获得二维数组的最佳选择。 - Mike Graham
1
经过几年偶尔编写严肃的Python应用程序后,标准Python数组的怪癖似乎值得直接使用numpy。+1 - WestCoastProjects

23

如果您对为什么不应该使用[['']*m]*n感到困惑。

Python使用一种称为“按对象引用调用”或“按赋值调用”的系统。(更多信息)

最好的方法是 [['' for i in range(columns)] for j in range(rows)]
这将解决所有问题。

更多说明:
例子:

>>> x = [['']*3]*3
[['', '', ''], ['', '', ''], ['', '', '']]
>>> x[0][0] = 1
>>> print(x)
[[1, '', ''], [1, '', ''], [1, '', '']]
>>> y = [['' for i in range(3)] for j in range(3)]
[['', '', ''], ['', '', ''], ['', '', '']]
>>> y[0][0]=1
>>> print(y)
[[1, '', ''], ['', '', ''], ['', '', '']]

1
谢谢你的解释。我一直在寻找这个。 - Darpan
1
Python 从不 使用按引用调用。 - juanpa.arrivillaga
1
感谢@juanpa.arrivillaga的提醒。 :) - Tushar

11

你可以只做这个:

[[element] * numcols] * numrows
例如:
>>> [['a'] *3] * 2
[['a', 'a', 'a'], ['a', 'a', 'a']]

但是这会产生一个不良的副作用:

>>> b = [['a']*3]*3
>>> b
[['a', 'a', 'a'], ['a', 'a', 'a'], ['a', 'a', 'a']]
>>> b[1][1]
'a'
>>> b[1][1] = 'b'
>>> b
[['a', 'b', 'a'], ['a', 'b', 'a'], ['a', 'b', 'a']]

14
据我的经验,这种“不良”影响常常是一些非常糟糕的逻辑错误的根源。我认为应该避免采用这种方法,而是选择@Vipul的回答会更好。 - Alan Turing
这种方法很有效,为什么有些人在评论中说它不好? - Bravo
2
由于不良的副作用,你不能真正将其视为矩阵。如果您不需要更改内容,则一切都会很好。 - hithwen
这是不好的,因为你在软拷贝矩阵中相同的行,改变一个元素将会改变所有其他的元素。 - Yar

10
twod_list = [[foo for _ in range(m)] for _ in range(n)]

当n为行数,m为列数,foo为值时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接