分享

探寻Python中如何同时迭代多个iterable对象

 imelee 2017-11-11

题外话:

最近因为课程需要开始深入了解Python语言。因为以前一直用的Java、C++等强类型的静态语言,现在突然使用Python确实感受到了很大的不同。

直观感觉就是,在Python中总是能找到一些让代码变得精巧、简洁、高效、美观的写法,使得初学者在写代码的过程充满了惊喜,从而渐渐喜欢上Python。而且Python的官方手册阅读起来感觉非常好,很多问题都描述的很清楚。不过总体来说,还是觉得Java大法好:)


Python中一个非常有用的语法就是for in循环跟iterable对象的结合,适当的使用它可以让代码变得更加赏心悦目。关于这一点一个被广泛引用的例子就是文本文件操作,这里也照搬一下。普通的文件操作:

  1. with open("test.txt") as fp:  
  2.     while True:  
  3.         line=fp.readline()  
  4.         if line=="":  
  5.             break  
  6.         do_something_with(line)  

引入for in循环以后的文件操作:

  1. with open("test.txt") as fp:  
  2.     for line in fp:  
  3.         do_something_with(line)  

明显的,代码一下子就简洁清晰了很多!由于Python里的fileiterable对象,所以可以直接被for in迭代。但是有个问题,这里的for in一次只能迭代一个序列。在这里也就是一次只能从一个文件里面读取一行,然后做一些处理。但是如果想每次从两个文件里面各读取一行,然后做一些处理,比如说文本对比,能做到吗?也就是说,Python中如何同时迭代多个序列,或者至少是同时迭代两个等长的序列?一个勉强可以接受的写法:

  1. with open("a.txt") as fa, open("b.txt") as fb:  
  2.     try:  
  3.         while True:  
  4.             do_something_with(fa.next(), fb.next())  
  5.     except StopIteration:  
  6.         pass  

但是还可以更加简洁一些吗?可以假想,如果语法支持下面的写法就好了:

  1. with open("a.txt") as fa, open("b.txt") as fb:  
  2.     for a,b in fa,fb:  
  3.         do_something_with(a, b)  

但实际上这是不行的,这里会得到“ValueError: too many values to unpack ”,因为in后面的fa,fb被看做了一个序列,也就是下面的语句是合法的:

  1. for fx in fa,fb  

这样一共循环两次,fx先后被赋值为fafb,不过这明显不是想要的效果。Python里面有一种构造序列的方法是:

  1. [a,b for a in fa for b in fb]  

这样构造的序列实际上是等效于两个循环嵌套得到的,循环结构等效于:

  1. for a in fa:  
  2.     for b in fb:  

Python里面还有什么东西是能够同时迭代多个序列的吗?想起来有个函数map(function, iterable, ...),它能够同时遍历给定的多个序列,每次都从每个序列中各取一个值组成一个元组对象,然后调用function并传入该对象。如果多个序列的长度不一样,那么所有其他序列都会被用None填充到最长序列的长度。利用map()函数代码可以简化为:

  1. with open("a.txt") as fa, open("b.txt") as fb:  
  2.     map(do_something_with, fa, fb)  

看上去真是好极了!但是map()在执行过程中会将每次调用function返回的值添加到一个list中,map()执行完毕以后就会返回这个list。然而这里并不需要这个list,就显得有些浪费了,不能仅仅为了追求代码的简短就放弃效能。但是还有别的办法吗?

其实理论上来说同时迭代多个序列这种功能,一般是不会在语言层面或者是核心库中提供支持的。因为这种行为不够基础和通用,程序员自己完全可以稍微写几行代码来实现,就像前面那样。那么退一步假设,如果真的没办法直接同时迭代多个序列,能不能把两个序列合并成一个二元组序列,然后迭代这个序列呢?想到了一个函数zip([iterable, ...])。正如刚才所说的,zip()能够把多个序列合并成一个新的list,新的list中每个元素都是一个元组对象。跟map()不一样的是,如果多个序列的长度不一样,那么最终返回的序列长度为最短序列的长度。利用zip()代码可以写成这样:

  1. with open("a.txt") as fa, open("b.txt") as fb:  
  2.     for a,b in zip(fa, fb):  
  3.         do_something_with(a, b)  

这就是一直想要达到的效果啊!但仔细一分析还是不对,zip()生成了一个新的list,而且这个list的尺寸至少是两个文件的尺寸之和!而这里需要的只是每次从两个文件里面各读取一行,然后作处理,处理完了以后,这两个行占用的空间就可以释放了,然后再继续各自读取下一行,所以这个过程本来是不会太消耗内存的。

这样也不行,还有什么办法呢?在网上找了很久,百度、必应、stackoverflow搜了好几遍,都没有找到满意的答案。就在快要绝望的时候,无意间看到stackoverflow上有人说他的一段代码有问题。只是大概扫了一眼,甚至都没仔细看他的完整描述,只注意到了一个单词——izip!当时脑子里就在想,这是什么?这个跟zip()函数有什么不同么?立马查询Python的文档,然后看到了一句瞬间惊爆眼球的话:

“跟zip()相似,但是返回一个iterator而非list”

有了itertools.izip(*iterable),一切都好说了,代码最终就是:

  1. from itertools import izip  
  2.   
  3. with open("a.txt") as fa, open("b.txt") as fb:  
  4.     for a,b in izip(fa, fb):  
  5.         do_something_with(a, b)  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多