如果你需要像这样进行微优化,唯一知道什么是最快的方法就是测试。
简短的版本是:append
比 快extend
,而 Joran Beasley 的建议itertools.chain.from_iterable
比任何一个都快——但前提是您将 替换map
为列表推导式。
所以:
import itertools
import timeit
def makestuff(count):
for i in range(count):
yield (i, i)
def f_extend(mystuff):
mylist = []
for x, y in mystuff:
mylist.extend([int(x), int(y)])
return mylist
def f_append(mystuff):
mylist = []
for x, y in mystuff:
mylist.append(int(x))
mylist.append(int(y))
return mylist
def f_chainmap(mystuff):
return list(map(int, itertools.chain(*mystuff)))
def f_chaincomp(mystuff):
return [int(x) for x in itertools.chain(*mystuff)]
def f_chainfrommap(mystuff):
return list(map(int, itertools.chain.from_iterable(mystuff)))
def f_chainfromcomp(mystuff):
return [int(x) for x in itertools.chain.from_iterable(mystuff)]
def f_reducecompcomp(mystuff):
return [int(x) for x in reduce(operator.iadd, (list(y) for y in mystuff), [])]
def f_reducecompmap(mystuff):
return [int(x) for x in reduce(operator.iadd, map(list, mystuff), [])]
try:
import numpy
def f_numpy(mystuff):
return numpy.array(mystuff).flatten().tolist()
def f_numpy2(mystuff):
return numpy.array(list(mystuff)).flatten().tolist()
except:
pass
if __name__ == '__main__':
import sys
main = sys.modules['__main__']
count = int(sys.argv[1]) if len(sys.argv) > 1 else 10000
for f in dir(main):
if f.startswith('f_'):
func = getattr(main, f)
mystuff = makestuff(count)
testfunc = lambda: func(mystuff)
print('{}: {}'.format(f, timeit.timeit(testfunc, number=count)))
对于 Python 2,我尝试了map
不带 extra 的版本list
,速度稍快,但仍然没有竞争力。当然,对于 Python 3,这list
是必要的。
以下是我的时间安排:
$ python testlister.py 1000000
f_append: 1.34638285637
f_chaincomp: 2.12710499763
f_chainfromcomp: 1.20806899071
f_chainfrommap: 2.77231812477
f_chainmap: 3.67478609085
f_extend: 1.38338398933
f_numpy: 5.52979397774
f_numpy2: 7.5826470852
f_reducecompcomp: 2.17834687233
f_reducecompmap: 3.16517782211
$ python3 ./testlister.py 1000000
f_append: 0.9949617639649659
f_chaincomp: 2.0521950440015644
f_chainfromcomp: 0.9724521590862423
f_chainfrommap: 2.5558998831082135
f_chainmap: 3.5766013460233808
f_extend: 1.149905970087275
f_reducecompcomp: 2.2112889911513776
f_reducecompmap: 1.9317334480583668
我python
的是苹果公司的 Python 2.7.2,而python3
python.org 3.3.0,都是 64 位,都在 OS X 10.8.2 上,在 2012 年中期的 MacBook Pro 上,2.2GHz i7 和 4GB。
如果您在 POSIX 平台上使用 32 位 Python,我过去曾注意到,在不远的过去,迭代器得到了优化,似乎加快了itertools
64 位构建中的许多事情,但在 32 位时减慢了它们的速度。因此,您可能会发现append
在这种情况下获胜。(与往常一样,在您真正关心优化的平台上进行测试。)
Ashwini Chaudhary 链接到Flattening a shallow list in Python,进一步链接到在 python 关联列表中有效地查找元素。我怀疑我的结果和他们的结果之间的部分差异是 2.6.0 和 2.7.2/3.3.0 之间迭代器的改进,但是我们明确使用 2 元素元素而不是更大的元素这一事实可能更重要.
此外,至少有一个答案声称这reduce
是最快的。原始帖子中的reduce
实现都非常慢,但我能够提出更快的版本。他们仍然无法与append
or竞争chain.from_iterable
,但他们在正确的范围内。
该f_numpy
函数是 heltonbiker 的实现。由于mystuff
是 2D 迭代器,这实际上只是生成了一个包装迭代器的 0D 数组,所以所numpy
能做的就是增加开销。我能够想出一个生成一维迭代器数组的实现,但这甚至更慢,因为现在所numpy
能做的就是经常增加 N 次开销。我可以获得整数的二维数组的唯一方法是list
先调用,如 in f_numpy2
,这会使事情变得更慢。(公平地说,在其他函数中添加额外list
的内容也会减慢它们的速度,但并不像使用 . 那样糟糕numpy
。)
不过,我这里很可能是空白,这里有合理的使用方式numpy
。当然,如果您可以确定顶层mystuff
或其中的每个元素mystuff
是 alist
或 a tuple
,您就可以编写更好的东西——如果您可以重新设计您的应用程序,让您首先拥有 2D numpy.array
,而不是一般的序列序列,那将是一个完全不同的故事。但是,如果您只有序列的一般 2D 迭代,那么对于这个用例来说似乎不是很好。