很多Python的程式員都會混淆 迭代器 和 生成器 的概念和作用,分不清到底兩個有什麼區別。今天我們來好好說一說這兩個概念。迭代器(Iterator)Iterator PatternIterator 是一種設計模式,它的作用是,提供一種順序訪問一個聚合對象中的各個元素,但又不需要暴露出其內部實現的... ...
很多Python的程式員都會混淆 迭代器
和 生成器
的概念和作用,分不清到底兩個有什麼區別。今天我們來好好說一說這兩個概念。
迭代器(Iterator)
Iterator Pattern
Iterator 是一種設計模式,它的作用是,提供一種順序訪問一個聚合對象中的各個元素,但又不需要暴露出其內部實現的方法。它是一種惰性的獲取數據的方法,我們不需要一次把所有的數據載入記憶體,這樣可以避免數據集太大,記憶體無法全部裝載的麻煩。
這種應用場景,比如:讀取一個大文件,分析每一行的關鍵字。
一個最簡單的迭代器模式,表現為一個介面,介面中包含兩個方法:
Next()
返回下一個元素hasNext()
返回是否還有下一個元素
實現了這種兩個方法的對象就是一個迭代器。
Python中的Iterator
在很多時候,Python程式員會忽略 迭代器(Iterator) 和 可迭代對象(Iterable Object) 的區別。
其實,我們要好好的區分一下他們兩個。
可迭代對象(Iterable Object)
可迭代對象是表示一個對象,擁有一次返回一個他自己的數據元素的能力。
例如:
In [1]: a = [1, 2, 3, 4, 5]
In [2]: for i in a:
...: print(i)
...:
1
2
3
4
5
In [3]: b = {"first":1, "second":2, "third":3}
In [4]: for i in b:
...: print i
...:
second
third
first
上面的代碼通過迭代的方式輸出了list中所有的元素,和dict中所有的key。所以,我們把list和dict叫做可迭代對象(不是迭代器)。
在Python中,所有的集合都可以迭代。在語言內部,迭代器支持下麵列出的操作:
- for迴圈
- 遍歷文件、目錄
- 列表推導、字典推導和集合推導
- 元組拆包
- 調用函數時,使用 * 拆包實參
- 構建和擴展集合類型
所以可以看到,迭代操作在python中很多地方都很重要。
序列可以迭代的原因
這依賴一個buildin-function iter()
。假如解釋器要迭代對象x,則會調用 iter()
產生一個迭代器,進行迭代。
內置的 iter 函數有以下作用:
- 檢查對象是否實現了
__iter__
方法,如果實現了就調用它,獲取一個迭代器。 - 如果沒有實現
__iter__
方法, 但是實現了__getitem__
方法, Python 會創建一個迭代 器,嘗試按順序(從索引 0 開始)獲取元素。 - 如果嘗試失敗, Python 拋出 TypeError 異常, 通常會提示“X object is not iterable”。
In [8]: x = 2
In [9]: iter(x)
-----------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-128770259dbe> in <module>()
----> 1 iter(x)
TypeError: 'int' object is not iterable
標準序列都實現了 __getitem__ 方法。 其實,它們也都實現了 __iter__ 方法,因此你也應該這麼做。之所以都實現 __getitem__ 是因為要向後相容,但後續可能廢棄。
如何實現可迭代對象
自己創建的 Object 如何變成一個可迭代的對象呢?如何自己創建一個迭代器呢?其實非常簡單。
對於可迭代對象,需要滿足下麵兩個要求的任意一個(原因參見上面):
- 擁有
__getitem__
方法;接受一個參數 index - 擁有
__iter__
方法;返回一個 Iterator
例:
#!/usr/bin/env python
class MyIterableObject():
def __init__(self, s):
self.seq = s.split(' ')
def __getitem__(self, index):
return self.seq[index]
def __iter__(self):
return MyIterator(self.seq) # MyIterator的具體實現參見後面
if __name__ == '__main__':
mio = MyIterableObject("a b c d e f g")
for i in mio:
print(i)
迭代器(Iterator)
當用 iter
函數獲取到一個迭代器之後,就可以操作迭代器來獲取對象的數據了。
使用 next()
方法來一個個的獲取元素。當所有元素獲取完畢,繼續調用 next()
方法的話,就會拋出一個 StopIteration 的異常。
如下:
In [13]: a = [1, 2, 3, 4, 5]
In [14]: i = iter(a)
In [15]: while True:
...: print(next(i))
...:
1
2
3
4
5
-----------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-15-ac43f8f9aeeb> in <module>()
1 while True:
----> 2 print(next(i))
3
StopIteration:
python的迭代器較為簡單,它並不支持重新定位到開始這樣的操作。如果一個迭代器一旦開始使用,如果想要從最開始讀取的話,只能創建一個新的迭代器了。
如何實現迭代器
標準的python迭代器需要實現兩個方法:
__iter__
返回迭代器本身next()
返回數據集中的下一個元素。如果沒有下一個了,則拋出一個 StopIteration
TIPS:
在 python3中 next() 方法的名稱,改為了__next__
,但是使用 python2 的方式依然可行。
例:
class MyIterator():
def __init__(self, s):
self.seq = s
self.len = len(self.seq)
self.index = 0
def __iter__(self):
return self
def next(self):
try:
n = self.seq[self.index]
except IndexError:
raise StopIteration
self.index += 1
return n
這裡有一點需要註意:迭代器模式描述中,需要有一個方法來判斷是否是最後一個元素,在python中使用異常代替了這個函數。在我們使用迭代器的過程中,捕獲這個異常即可。如果使用 buildin 的 for .. in
方式的話,它會自動幫我們捕獲。
生成器(Generator)
首先,我們平常說起來 Generator
這個東西的時候,其實,它一般指代兩個東西:
- Generator Function: 一個函數,在定義時使用了
yield
關鍵字,則成為這個函數為 生成器函數 - Generator Object: 由 Generator Function 生成的,是一個特殊的 Iterator。它包裝了 生成器函數 的定義體,並實現了
__iter__
和next
兩個方法,符合 Iterator 的協議。
生成器和迭代器最大的不同在哪裡呢?
主要是對於值產生的方法不一樣。當使用迭代器時,所有要迭代的元素必須是已經存在的。而對於生成器來說,每個值不必已經存在,可以在執行的過程中計算(生成)出來。
比如:用生成器 生成一個等比數列
def arithmetic_progression(base, dif, count):
for n in range(count):
yield base + dif * n
if __name__ == '__main__':
for i in arithmetic_progression(1, 3, 10):
print(i)
可以看到這個等比數列是不存在的,是在迭代的過程中每次執行到 yield 的時候,計算出來的。
可以達到這樣的特性歸功於 yield
關鍵字。它可以將執行的函數暫停,並返回值,下一次從中斷的地方繼續。它的執行流程如下:
- 使用 next 調用 生成器函數
- 函數 執行到 yield,會返回一個值,並暫停函數
- 重覆 1-2 步,直到所有的值都返回完畢
- 如果使用 next,則會拋出 StopIteration
代碼驗證如下:
In [21]: def test():
...: yield 1
...: yield 2
...: yield 3
...:
In [22]: gen = test()
In [23]: next(gen)
Out[23]: 1
In [24]: next(gen)
Out[24]: 2
In [25]: next(gen)
Out[25]: 3
In [26]: next(gen)
-----------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-26-8a6233884a6c> in <module>()
----> 1 next(gen)
StopIteration:
用生成器代替迭代器
現在我們用生成器來替代上面的 迭代器方案 MyIterableObject
。
class MyGenerator():
def __init__(self, s):
self.seq = s.split(' ')
def __iter__(self):
for s in self.seq:
yield s
代碼簡化了很多,我們不需要再自己創建 Iterator 對象,yield會幫我們做這些。
迭代器工具集(itertools)
雖然,生成器的使用已經夠簡單了,但是像python這種節省你生命時間的語言,怎麼會沒有更進一步的包裝出來?
python內置了非常多的生成器函數,比如遍歷文件夾的 os.walk
,工具類的有 map
、enumerate
等等。
python還有一個官方庫,叫做 itertools,它包含了 19個 生成器函數,可以組合完成各樣的功能。
結尾
以上,就是 迭代器和生成器的區別。其實,這兩個東西並不難理解。但是,這裡面有幾個比較容易混淆的概念。只要搞清楚了這些概念,就能區分得很清楚啦!
作者和出處(reposkeeper) 授權分享 By CC BY-SA 4.0
關註微信公眾號,獲取新文章的推送!