用拉链来并行处理迭代器


经常地,你会在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函数,有如下两个问题。

  • 一是在Python2zip并不是一个生成器,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的截断行为是不好的,令人吃惊的。如果你不能保证你正处理的列表的长度一致,那么我建议你使用内置模块itertoolszip_longest函数来代替(在Python2中称为izip_longest)。


备忘录

  • 内置的zip函数可以并行的对多个迭代器进行处理。
  • Python3中,懒模式生成器获得的是元组;而在Python2中,zip返回的是一个包含了其处理好的所有元祖的一个集合。
  • 如果所处理的迭代器的长度不一致时,zip会默认截断输出,使得长度为最先到达尾部的那个长度。
  • 内置模块itertools中的zip_longest函数可以并行地处理多个迭代器,而可以无视长度不一致的问题。

results matching ""

    No results matching ""