如何并行遍历两个列表?
我有两个可迭代的对象,想要成对地遍历它们:
foo = [1, 2, 3]
bar = [4, 5, 6]
for (f, b) in iterate_together(foo, bar):
print("f:", f, " | b:", b)
这样应该会得到:
f: 1 | b: 4
f: 2 | b: 5
f: 3 | b: 6
一种方法是通过索引来遍历:
for i in range(len(foo)):
print("f:", foo[i], " | b:", bar[i])
但我觉得这样有点不够“Python风格”。有没有更好的方法呢?
相关任务:
* 如何将列表合并成元组列表? - 给定上面的 foo
和 bar
,创建列表 [(1, 4), (2, 5), (3, 6)]
。
* 如何从单独的键和值列表创建字典(dict)? - 创建字典 {1: 4, 2: 5, 3: 6}
。
* 使用推导式创建字典 - 使用 zip
在字典推导式中构建 dict
。
9 个回答
基于@unutbu的回答,我比较了在使用Python 3.6的zip()
函数时,两个相同列表的迭代性能。比较的方式包括Python的enumerate()
函数、手动计数器(见count()
函数)、使用索引列表,以及在特殊情况下,一个列表(foo
或bar
)的元素可能用来索引另一个列表。我们使用timeit()
函数来调查它们在打印和创建新列表时的性能,重复次数为1000次。下面是我为这些调查创建的Python脚本之一。foo
和bar
列表的大小范围从10到1,000,000个元素。
结果:
打印性能:所有考虑的方法在打印时的性能与
zip()
函数大致相似,误差范围为+/-5%。当列表大小小于100个元素时,情况有所不同。在这种情况下,索引列表的方法比zip()
函数稍慢,而enumerate()
函数则快约9%。其他方法的性能与zip()
函数相似。创建列表的性能:我们探讨了两种创建列表的方法:使用(a)
list.append()
方法和(b) 列表推导式。在考虑了+/-5%的误差后,发现zip()
函数的性能比enumerate()
函数、使用索引列表和手动计数器都要快。zip()
函数在这些比较中性能提升可达5%到60%。有趣的是,使用foo
的元素来索引bar
的性能可以与zip()
函数相当或更快(5%到20%)。
理解这些结果:
程序员需要确定每个操作的计算时间是否有意义或重要。
例如,对于打印目的,如果这个时间标准是1秒,也就是10**0秒,那么我们可以看左侧图表的y轴,在1秒的位置水平延伸到单项曲线,我们会发现列表大小超过144个元素时,会产生显著的计算成本和对程序员的重要性。也就是说,对于较小的列表大小,这次调查中提到的方法所获得的性能提升对程序员来说是微不足道的。程序员会得出结论,zip()
函数在迭代打印语句时的性能与其他方法相似。
结论
在创建列表时,使用zip()
函数并行迭代两个列表可以获得显著的性能提升。当并行迭代两个列表以打印它们的元素时,zip()
函数的性能与enumerate()
函数、手动计数器、索引列表以及在特殊情况下使用一个列表的元素来索引另一个列表的性能相似。
用于调查列表创建的Python 3.6脚本。
import timeit
import matplotlib.pyplot as plt
import numpy as np
def test_zip( foo, bar ):
store = []
for f, b in zip(foo, bar):
#print(f, b)
store.append( (f, b) )
def test_enumerate( foo, bar ):
store = []
for n, f in enumerate( foo ):
#print(f, bar[n])
store.append( (f, bar[n]) )
def test_count( foo, bar ):
store = []
count = 0
for f in foo:
#print(f, bar[count])
store.append( (f, bar[count]) )
count += 1
def test_indices( foo, bar, indices ):
store = []
for i in indices:
#print(foo[i], bar[i])
store.append( (foo[i], bar[i]) )
def test_existing_list_indices( foo, bar ):
store = []
for f in foo:
#print(f, bar[f])
store.append( (f, bar[f]) )
list_sizes = [ 10, 100, 1000, 10000, 100000, 1000000 ]
tz = []
te = []
tc = []
ti = []
tii= []
tcz = []
tce = []
tci = []
tcii= []
for a in list_sizes:
foo = [ i for i in range(a) ]
bar = [ i for i in range(a) ]
indices = [ i for i in range(a) ]
reps = 1000
tz.append( timeit.timeit( 'test_zip( foo, bar )',
'from __main__ import test_zip, foo, bar',
number=reps
)
)
te.append( timeit.timeit( 'test_enumerate( foo, bar )',
'from __main__ import test_enumerate, foo, bar',
number=reps
)
)
tc.append( timeit.timeit( 'test_count( foo, bar )',
'from __main__ import test_count, foo, bar',
number=reps
)
)
ti.append( timeit.timeit( 'test_indices( foo, bar, indices )',
'from __main__ import test_indices, foo, bar, indices',
number=reps
)
)
tii.append( timeit.timeit( 'test_existing_list_indices( foo, bar )',
'from __main__ import test_existing_list_indices, foo, bar',
number=reps
)
)
tcz.append( timeit.timeit( '[(f, b) for f, b in zip(foo, bar)]',
'from __main__ import foo, bar',
number=reps
)
)
tce.append( timeit.timeit( '[(f, bar[n]) for n, f in enumerate( foo )]',
'from __main__ import foo, bar',
number=reps
)
)
tci.append( timeit.timeit( '[(foo[i], bar[i]) for i in indices ]',
'from __main__ import foo, bar, indices',
number=reps
)
)
tcii.append( timeit.timeit( '[(f, bar[f]) for f in foo ]',
'from __main__ import foo, bar',
number=reps
)
)
print( f'te = {te}' )
print( f'ti = {ti}' )
print( f'tii = {tii}' )
print( f'tc = {tc}' )
print( f'tz = {tz}' )
print( f'tce = {te}' )
print( f'tci = {ti}' )
print( f'tcii = {tii}' )
print( f'tcz = {tz}' )
fig, ax = plt.subplots( 2, 2 )
ax[0,0].plot( list_sizes, te, label='enumerate()', marker='.' )
ax[0,0].plot( list_sizes, ti, label='index-list', marker='.' )
ax[0,0].plot( list_sizes, tii, label='element of foo', marker='.' )
ax[0,0].plot( list_sizes, tc, label='count()', marker='.' )
ax[0,0].plot( list_sizes, tz, label='zip()', marker='.')
ax[0,0].set_xscale('log')
ax[0,0].set_yscale('log')
ax[0,0].set_xlabel('List Size')
ax[0,0].set_ylabel('Time (s)')
ax[0,0].legend()
ax[0,0].grid( b=True, which='major', axis='both')
ax[0,0].grid( b=True, which='minor', axis='both')
ax[0,1].plot( list_sizes, np.array(te)/np.array(tz), label='enumerate()', marker='.' )
ax[0,1].plot( list_sizes, np.array(ti)/np.array(tz), label='index-list', marker='.' )
ax[0,1].plot( list_sizes, np.array(tii)/np.array(tz), label='element of foo', marker='.' )
ax[0,1].plot( list_sizes, np.array(tc)/np.array(tz), label='count()', marker='.' )
ax[0,1].set_xscale('log')
ax[0,1].set_xlabel('List Size')
ax[0,1].set_ylabel('Performances ( vs zip() function )')
ax[0,1].legend()
ax[0,1].grid( b=True, which='major', axis='both')
ax[0,1].grid( b=True, which='minor', axis='both')
ax[1,0].plot( list_sizes, tce, label='list comprehension using enumerate()', marker='.')
ax[1,0].plot( list_sizes, tci, label='list comprehension using index-list()', marker='.')
ax[1,0].plot( list_sizes, tcii, label='list comprehension using element of foo', marker='.')
ax[1,0].plot( list_sizes, tcz, label='list comprehension using zip()', marker='.')
ax[1,0].set_xscale('log')
ax[1,0].set_yscale('log')
ax[1,0].set_xlabel('List Size')
ax[1,0].set_ylabel('Time (s)')
ax[1,0].legend()
ax[1,0].grid( b=True, which='major', axis='both')
ax[1,0].grid( b=True, which='minor', axis='both')
ax[1,1].plot( list_sizes, np.array(tce)/np.array(tcz), label='enumerate()', marker='.' )
ax[1,1].plot( list_sizes, np.array(tci)/np.array(tcz), label='index-list', marker='.' )
ax[1,1].plot( list_sizes, np.array(tcii)/np.array(tcz), label='element of foo', marker='.' )
ax[1,1].set_xscale('log')
ax[1,1].set_xlabel('List Size')
ax[1,1].set_ylabel('Performances ( vs zip() function )')
ax[1,1].legend()
ax[1,1].grid( b=True, which='major', axis='both')
ax[1,1].grid( b=True, which='minor', axis='both')
plt.show()
你需要用到 zip
这个函数。
for (f,b) in zip(foo, bar):
print "f: ", f ,"; b: ", b
Python 3
for f, b in zip(foo, bar):
print(f, b)
zip
这个函数会在foo
或bar
中较短的那个结束时停止。
在Python 3中,zip
返回的是一个元组的迭代器,类似于Python 2中的itertools.izip
。如果你想得到一个元组的列表,可以用list(zip(foo, bar))
。如果你想在两个迭代器都用完之前继续合并,可以使用itertools.zip_longest。
Python 2
在Python 2中,zip
返回的是一个元组的列表。当foo
和bar
都不大的时候,这样是没问题的。如果它们都很大,那么用zip(foo, bar)
会创建一个不必要的大临时变量,这时候应该用itertools.izip
或者itertools.izip_longest
,它们返回的是迭代器,而不是列表。
import itertools
for f,b in itertools.izip(foo,bar):
print(f,b)
for f,b in itertools.izip_longest(foo,bar):
print(f,b)
izip
会在foo
或bar
中的任意一个用完时停止。izip_longest
会在foo
和bar
都用完时停止。当较短的迭代器用完时,izip_longest
会返回一个包含None
的元组,None
的位置对应于那个用完的迭代器。如果你想的话,也可以设置一个不同的fillvalue
,而不是None
。详细信息可以查看这里。
另外要注意的是,zip
和类似的函数可以接受任意数量的可迭代对象作为参数。例如,
for num, cheese, color in zip([1,2,3], ['manchego', 'stilton', 'brie'],
['red', 'blue', 'green']):
print('{} {} {}'.format(num, color, cheese))
会打印
1 red manchego
2 blue stilton
3 green brie