探尋Python中如何同時迭代多個iterable對象

題外話:

最近因爲課程需要開始深入瞭解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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章