频道栏目
首页 > 资讯 > 代码收藏 > 正文

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

16-01-07        来源:[db:作者]  
收藏   我要投稿

题外话:

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

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


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

with open("test.txt") as fp:
	while True:
		line=fp.readline()
		if line=="":
			break
		do_something_with(line)

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

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

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

with open("a.txt") as fa, open("b.txt") as fb:
	try:
		while True:
			do_something_with(fa.next(), fb.next())
	except StopIteration:
		pass

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

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

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

for fx in fa,fb

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

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

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

for a in fa:
	for b in fb:

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

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

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

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

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

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

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

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

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

from itertools import izip

with open("a.txt") as fa, open("b.txt") as fb:
	for a,b in izip(fa, fb):
		do_something_with(a, b)
相关TAG标签
上一篇:drupal,该怎么处理
下一篇:phpMyAdmin - 异常“缺少 mysqli 扩展”()
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站