Python:创建一个包含n个列表的列表的最快方法

111

我想知道如何创建一列空列表:

[[],[],[]...]

因为Python在内存中处理列表的方式,所以这段代码无法运行:

[[]]*n

这确实创建了[[],[],...],但每个元素都是相同的列表:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

可以使用类似列表推导式的语法:

d = [[] for x in xrange(0,n)]

但是这个方法使用了Python虚拟机进行循环。有没有一种方式可以使用隐式循环(利用它是用C语言编写的)?

d = []
map(lambda n: d.append([]),xrange(0,10))

这实际上更慢. :(


1
如果有比 d = [[] for x in xrange(0,n)] 更快的东西,我会感到惊讶。你要么必须在 Python 中显式循环,要么重复调用 Python 函数/lambda(这应该会更慢)。但仍然希望有人能发帖证明我是错的 :). - MAK
1
当您使用timeit进行测量时,您学到了什么? - S.Lott
我刚刚确认map(lambda x: [], xrange(n))比列表推导式慢。 - Andrew Clark
5个回答

115

这可能是比其他方式稍微快一点的方法

d = [[] for x in xrange(n)]

from itertools import repeat
d = [[] for i in repeat(None, n)]

在每次迭代中,它不必创建一个新的int对象,而且在我的机器上大约快15%。

编辑:使用NumPy,您可以避免使用Python循环。

d = numpy.empty((n, 0)).tolist()

但实际上,这比列表推导式慢了2.5倍。


map(lambda x:[], repeat(None,n)) 怎么样? - PaulMcG
3
@Paul:由于lambda表达式的函数调用开销,这将再次变得更慢。 - Sven Marnach
这个应该更新了,因为在Python 3中range已经不同了,对吧? - beruic
@beruic 我只是在第一个代码块中引用了问题中的代码,所以更改它并没有太多意义。 - Sven Marnach

12
列表推导式实际上比显式循环实现更高效(请参见示例函数的dis输出),而map方式必须在每次迭代中调用不透明可调用对象,这会产生相当大的开销。
无论如何,[[] for _dummy in xrange(n)]是正确的方法,各种其他方法之间微小的(如果存在)速度差异不应该有影响。除非你大部分时间都在做这个 - 但在这种情况下,你应该改进你的算法。你有多经常创建这些列表?

5
请不要把“_”作为变量名!其他方面都很好,回答不错。 - Sven Marnach
14
@Sven: 为什么不行呢?这常用于未使用的变量(如果它被称为 i,我会寻找它在哪里被使用)。唯一的陷阱是它会遮盖REPL中持有上一个结果的 _...而这只在2.x版本中的列表推导中会出现泄漏。 - user395760
并不是很频繁,这就是为什么我使用了列表推导式。虽然我认为看看人们的想法会很有趣。我在 PyCon 上看到了 Dropbox 的演讲,其中使用 itertools.imap 而不是 for 循环来更新 md5 哈希值,次数非常之多,从那以后我对 C 循环有点着迷。 - munchybunch
12
不使用它的最重要原因是它会让人们混淆,误以为它是某种特殊语法。此外,与交互式解释器中的“_”冲突之外,它还与常见的gettext别名冲突。如果想明确变量是虚拟变量,请将其命名为“dummy”,而不是“_”。 - Sven Marnach

12

这里有两种方法,一种简单而甜美(概念性的),另一种更为正式,在读取数据集后可以应用于多种情况。

方法 1:概念性

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

方法二: 正式且可扩展

另一种优雅的方式是将列表作为来自文件的不同数字列表的列表存储。(这里的文件是数据集train) Train 是一个数据集,例如有 50 行和 20 列。即 Train[0] 给出 csv 文件的第一行,Train[1] 给出第二行,以此类推。我想将包含 50 行数据集分离为一个列表,但要排除第 0 列,因为它是我的解释变量,因此必须从原始训练数据集中删除,然后逐个列表进行扩展 - 即生成一个列表的列表。这里是执行此操作的代码。

请注意,我在内部循环中从“1”开始读取,因为我只对解释变量感兴趣。在其他循环中重新初始化 X1=[],否则 X2.append([0:(len(train[0])-1)]) 将重复重写 X1 - 除此之外更节省内存。

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])

7

要创建列表和列表的列表,请使用以下语法

     x = [[] for i in range(10)]

这将创建一个一维列表,并在[[number]]中放置数字来初始化它,设置列表长度请在range(length)中输入长度。

  • 要创建列表的列表,请使用以下语法。
    x = [[[0] for i in range(3)] for i in range(10)]

这将用值0初始化一个10*3维的列表嵌套列表

  • 访问/操作元素:
    x[1][5]=value

2

所以我进行了一些速度比较,以找到最快的方法。 列表推导确实非常快。唯一接近的方法是在构建列表时避免执行字节码。 我的第一个尝试是以下方法,原则上似乎更快:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(当然)生成一个长度为2 ** n的列表。

根据timeit的测试结果,无论是短列表还是长(一百万个元素)列表,这种方式的速度都是列表推导式的两倍慢。

我的第二次尝试是使用starmap来调用列表构造函数。有一种构造方法可以以最快的速度运行列表构造函数,但仍然比列表推导式慢,但只是微不足道的差距:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

有趣的是,执行时间表明最后一次列表调用使星图解决方案变慢,因为它的执行时间几乎完全等于以下速度:
l = list([] for _ in range(1<<n))

我的第三次尝试是当我意识到list(())也会生成一个列表时,我尝试了以下这个看起来简单的方法:

l = list(map(list, [()]*(1<<n)))

但是这比星图调用要慢。

结论:对于追求速度的人: 使用列表推导式。 只有在必要时才调用函数。 使用内置函数。


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