用拉链来并行处理迭代器
经常地,你会在Python
中发现自己有许多相关的对象列表。列表表达式可以使得在获取源数据列表和通过赋值表达式(详见第7项:是用列表表达式而不是map
或者filter
)而衍生的列表处理上变的相当的容易。
names = ['Cecilia', 'Lise', 'Marie']
letters = [ len(n) for n in names]
这些衍生的数据项与其源数据项也是息息相关的,这与其索引相关。为了并行的迭代这两个列表,你可以遍历此源列表。
longest_name = None
max_letters = 0
for i in range(len(names)):
count = letters[i]
if count > max_letters:
longest_name = names[i]
max_letters = count
print(longest_name)
>>>
Cecilia
但是问题是,在循环过程中代码出现的阅读上的干扰忒多。names
的下标以及letters
使得代码难于阅读。并且循环中将数组索引化发生了两次。使用enumerate
(详见第10项:使用enumerate
而不是range
)函数可以稍微的改善这个状况,但是效果并不是特别的理想。
for i, name in enumerate(names):
count = letters[i]
if count > max_letters:
longest_name = name
max_letters = count
为了使代码更加的干净,Python
提供了一个内置的拉链式的函数。在Python3
中,zip
通过懒模式生成器封装了两个或者更多的迭代器。zip
生成器字段元祖包含了每一个迭代器的下一个元素的值,现在代码比刚才的使用的索引了两个列表的案例变的更加的清晰咯。
for name,count in zip(names,letters):
if count > max_letters:
longest_name = name
max_letters = count
但是,关于内置的zip
函数,有如下两个问题。
一是在
Python2
中zip
并不是一个生成器,zip
函数将完全的使用提供的迭代器,并且返回由它创建的所有元组而组成的一个集合。这很有可能由于自身使用了太多的内存空间而导致问题的发生,甚至崩溃。如果你想在Python2
中处理较大的迭代器,那么你应该使用itertools
模块中的izip
函数(详见第46项:使用内置的算法和数据结构)。另一个问题是如果输入序列的长度不一致的时候,
zip
表现的将会很奇怪。例如:你在上面的集合中添加了一个新的name
值,但是忘记了更新letter
的数量,此时运行代码的话就会出现意想不到的结果。names.append("Rosalind") for name, count in zip(names,letters): print(name) >>> Cecilia Lise Marie
而新添加的值
Rosalind
并没有出现在结果集中。这就是zip
的工作方式,它将维持原来的字段元祖直到其中一个包装的迭代器走到了尽头。也就是默认会截断输出,剩下的数据将不予展示。如果你知道被处理的迭代器的长度一致(通常是被列表表达式衍生出来的)时,这个方法运行的将会很好。在很多其他情形下,zip
的截断行为是不好的,令人吃惊的。如果你不能保证你正处理的列表的长度一致,那么我建议你使用内置模块itertools
的zip_longest
函数来代替(在Python2
中称为izip_longest
)。
备忘录
- 内置的
zip
函数可以并行的对多个迭代器进行处理。 - 在
Python3
中,懒模式生成器获得的是元组;而在Python2
中,zip
返回的是一个包含了其处理好的所有元祖的一个集合。 - 如果所处理的迭代器的长度不一致时,
zip
会默认截断输出,使得长度为最先到达尾部的那个长度。 - 内置模块
itertools
中的zip_longest
函数可以并行地处理多个迭代器,而可以无视长度不一致的问题。