經過數個版本的發展,Python 的 「惰性」 大大增強。有幾個版本中已出現了在函數體中用 yield 語句定義的生成器。但是,在這個過程中,還出現了 itertools 模塊,它可以組合和創建各種類型的迭代器。還出現了 iter() 內置函數,它可以將許多類似於序列的對象轉換為迭代器。在 Python 2.4 中,出現了生成器表達式(generator expression);在 2.5 中,出現了改進的生成器,這使編寫協同例程更為輕鬆。另外,越來越多的 Python 對象成為迭代器或類迭代器,例如,過去讀取文件需要使用 .xreadlines() 方法或 xreadlines 模塊,而現在 open() 的默認行為就能是讀取文件。
同樣,過去要實現 dict 的惰性循環遍歷需要使用 .iterkeys() 方法;現在,它是默認的 for key in dct 行為。xrange() 這樣的函數與生成器類似的方面有些 「特殊」,它們既不是真正的 迭代器(沒有 .next() 方法),也不是實際的列表(比如 range() 返回的列表)。但是,enumerate() 返回一個真正的生成器,通常會實現以往希望 xrange() 實現的功能。itertools.count() 是另一個惰性調用,它的作用與 xrange()幾乎 完全相同,但它是一個功能完備的迭代器。
Python 的發展大方向是以惰性方式構造類似於序列的對象;總體來說,這個方向是正確的。惰性的偽序列既可以節省內存空間,還可以提高操作的速度(尤其是在處理非常大的與序列類似的 「東西」 時)。
問題在於,Python 在判斷 「硬」 序列和迭代器之間的差異和相似性方面仍然不完善。這個問題最棘手的部分是,它實際上違背了 Python 的 「duck typing」 思想:只要給定的對象具有正確的行為,就能夠將它用於特定的用途,而不存在任何繼承或類型限制。迭代器或類迭代器有時表現得像序列,但有時候不是;反過來說,序列常常表現得像迭代器,但並非總是如此。表現不像迭代器那些序列已涉及 Python 的神秘領地,其作用尚不明朗。
分歧所有類序列或類迭代器的主要相似之處是,它們都允許進行循環遍歷,無論是使用 for 循環、列表理解(list comprehension)還是生成器理解(generator comprehension)。除此之外,就出現了分歧。其中最重要的差異是,序列既可編制索引,也可直接切片(slice),而迭代器不能。實際上,為序列編制索引可能是最常用的序列操作 —— 究竟為什麼在迭代器上無法使用索引呢?例如:
清單 9. 與序列和迭代器相似的東西1
2
3
4
5
6
7
8
9
>>> r = range(10)
>>> i = iter(r)
>>> x = xrange(10)
>>> g = itertools.takewhile(lambda n: n<10, itertools.count())
#...etc...
對於所有這一切,都可以使用 for n in thing。實際上,如果用 list(thing) 顯示它們,會得到完全相同的結果。但是,如果希望獲得其中的一個特定條目(或一些條目的切片),就需要考慮 thing 的具體類型。例如:
清單 10. 索引操作成功和失敗的示例1
2
3
4
5
>>> r[4]
4
>>> i[4]
TypeError: unindexable object
對於每種序列/迭代器類型,只要費一番功夫,總能夠獲得其中的特定條目。一種方法是進行循環,直至到達所需的對象。另一種古怪的解決方案如下:
清單 11. 獲得索引的怪方法1
2
3
4
>>> thing, temp = itertools.tee(thing)
>>> zip(temp, '.'*5)[-1][0]
4
對 itertools.tee() 的預調用保留了原始迭代器。對於切片,可以按照特殊方式使用 itertools.islice() 函數。
清單 12. 獲得一個切片的特殊方法1
2
3
4
5
>>> r[4:9:2]
[4, 6, 8]
>>> list(itertools.islice(r,4,9,2)) # works for iterators
[4, 6, 8]