Python如何优化条件列表推导式

15

我读了有关Python中无需使用[ ]的列表推导式的文章,现在我知道了


''.join([str(x) for x in mylist])
比...更快
''.join(str(x) for x in mylist)

因为“列表推导式高度优化”,所以我认为优化依赖于解析for表达式,查看mylist,计算其长度,并使用它来预分配确切的数组大小,这节省了大量重新分配。

当使用''.join(str(x) for x in mylist)时,join盲目地接收生成器并必须在不知道大小的情况下构建其列表。

但是现在考虑这个:

mylist = [1,2,5,6,3,4,5]
''.join([str(x) for x in mylist if x < 4])
Python如何确定列表推导的大小?它是根据mylist的大小计算的,然后在进行迭代时缩小(如果条件过滤掉99%的元素,则可能非常糟糕),还是会回到“事先不知道大小”的情况?
编辑:我进行了一些小型基准测试,似乎证实了存在优化:
没有条件:
import timeit

print(timeit.timeit("''.join([str(x) for x in [1,5,6,3,5,23,334,23234]])"))
print(timeit.timeit("''.join(str(x) for x in [1,5,6,3,5,23,334,23234])"))
产出(如预期):
3.11010817019474
3.3457350077491026

有一个条件:

print(timeit.timeit("''.join([str(x) for x in [1,5,6,3,5,23,334,23234] if x < 50])"))
print(timeit.timeit("''.join(str(x) for x in [1,5,6,3,5,23,334,23234] if x < 50)"))
产生:
2.7942209702566965
3.0316467566203276
所以条件列表推导仍然更快。

1
这个回答解决了你的问题吗:列表推导式与生成器表达式的奇怪timeit结果? - Moinuddin Quadri
还不错,但在这个问题中,for循环后面从来没有条件。只有在表达式本身中,这意味着大小是预先知道的。 - Jean-François Fabre
1
我认为Python处理for x in xx for y in yy的行为在使用链接问题和for x in y if x<123的行为应该是相似的,因为在这两种情况下,Python在表达式被评估之前不知道结果列表的大小。 (这只是逻辑假设,不确定是否正确) - Moinuddin Quadri
我建议您查看源代码:https://github.com/python/cpython/blob/master/Objects/stringlib/join.h - Mazdak
@Kasramvd 我知道join如果还不是列表,它会创建一个列表来确定大小。问题是关于列表推导式在有条件语句时如何计算大小的。现在我开始相信最坏情况下会分配空间大小,并且在后面缩小列表大小。 - Jean-François Fabre
2
@Jean-FrançoisFabre,我明白了您的问题。如果您深入源码研究,您会发现需要寻找PyBytes_GET_SIZE,然后您可能会找到答案。 - Mazdak
1个回答

13
  • 列表推导式即使可以预先确定大小,也不会预先分配内存空间。您假定存在一种未实际执行的优化。
  • 列表推导式更快是因为迭代器机制和进入/退出生成器表达式堆栈帧的工作都会产生成本。而列表推导式不需要承担这些成本。

2
那么,在预分配列表方面是否可能存在实质性的收益呢?正如@Jean-François所指出的那样,“如果列表很大且条件过滤掉99%的元素,则可能是不好的”,但是另一方面,如果条件仅过滤掉1%的元素,则可能是好的 - Jongware
@RadLexus 没错:如果被迭代的集合是一个list或者其他已知大小且没有条件的数据结构,那么不需要使用双重“for”循环,可以将其视为一种特殊情况(非常常见),预先分配列表大小。这在现有代码中适用的情况高达80%! - Jean-François Fabre

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