python中同时迭代对个iterable对象_F_hawk189_新浪博客

时间:2022-11-16 11:56:33



题外话:

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

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


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的尺寸至少是两个文件的尺寸之和!而这里需要的只是每次从两个文件里面各读取一行,然后作处理,处理完了以后,这两个行占用的空间就可以释放了,然后再继续各自读取下一行,所以这个过程本来是不会太消耗内存的。

这样也不行,还有什么办法呢?在网上找了很久,百度、必应、*搜了好几遍,都没有找到满意的答案。就在快要绝望的时候,无意间看到*上有人说他的一段代码有问题。只是大概扫了一眼,甚至都没仔细看他的完整描述,只注意到了一个单词——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)