Python迭代器和生成器
Python的迭代器整合在語言之中,迭代器和生成器是Python中很重要的用法,本文將深入瞭解迭代器和生成器。
首先,我們都知道for迴圈是一個基礎迭代操作,大多數的容器物件都可以使用for迴圈,那麼,我們從for迴圈開始:
你有沒有想過,for迴圈的內部實現原理呢?
其實,在Python中,for迴圈是對迭代器進行迭代的語法糖,內部執行機理就是:首先底層對迴圈物件實現迭代器包裝(呼叫容器物件的__iter__
方法)返回一個迭代器物件,每迴圈一步,就呼叫一次迭代器物件的__next__
方法,直到迴圈結束時,自動處理StopIteration這個異常。
對於像list,dict等容器物件而言,都可以使用for迴圈,但是它們並不是迭代器,它們屬於可迭代物件。
什麼是可迭代物件呢?
最簡單的解釋:實現了迭代方法可以被迭代的物件,可以使用isinstance()方法進行判斷。
舉個例子:
In [1]: from collections import Iterable, Iterator
In [2]: a = [1, 2, 3]
In [3]: isinstance(a, Iterable)
Out[3]: True
In [4]: b = a.__iter__()
In [5]: isinstance(b, Iterator)
Out[5]: True
可迭代物件實現了__iter__
方法,該方法返回一個迭代器物件。
以上,可以看到,在迭代過程中,實際呼叫了迭代器的__next__
那麼,什麼是迭代器?
實現了迭代器協議的物件就是迭代器,所謂的迭代器協議可以簡單歸納為:
- 實現
__iter__()
方法,返回一個迭代器 - 實現next方法,返回當前元素並指向下一個元素,如果當前位置已無元素,則丟擲StopIteration異常 。
迭代器和可迭代物件的區別是:迭代器可以使用next()方法不斷呼叫並返回下一個值,除了呼叫可迭代物件的__iter__
方法來將可迭代物件轉換為迭代器以外,還可以使用iter()方法。
舉個例子來驗證以上說法:
In [1]: iter_data = iter([1, 2, 3]) In [2]: print(next(iter_data)) 1 In [3]: print(next(iter_data)) 2 In [4]: print(next(iter_data)) 3 In [5]: print(next(iter_data)) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-16-425d66e859b8> in <module> ----> 1 print(next(iter_data))
為什麼要用迭代器?
很重要的一點是,Python把迭代器內建在語言之中的,我們在遍歷一個容器物件時並不需要去實現具體的遍歷操作。
迭代器時一個惰性序列,僅僅在迭代至當前元素時才計算該元素的值,在此之前可以不存在,在此之後可以隨時銷燬,也就是說,在迭代過程中不是將所有元素一次性載入,這樣便不需要考慮記憶體的問題。通過定義迭代器協議,我們可以隨時實現一個迭代器。
什麼時候用迭代器?
具體在什麼場景下可以使用迭代器:
- 數列的資料規模巨大
- 數列有規律,但是不能使用列表推導式描述。
舉個最簡單的例子:
class Fib(object):
def __init__(self):
self._a = 0
self._b = 1
def __iter__(self):
return self
def __next__(self):
self._a, self._b = self._b, self._a + self._b
return self._a
if __name__ == '__main__':
for index, item in enumerate(Fib()):
print(item)
if index >= 9:
break
什麼是生成器?
生成器,顧名思義,就是按照一定的模式生成一個序列,是一種高階的迭代器,Python中有一個專門的關鍵字(yield)來實現生成器。
如果一個函式,使用了yield語句,那麼它就是一個生成器函式,當呼叫生成器函式函式時,它返回一個迭代器,不過這個迭代器時一個生成器物件。
舉個例子:
from itertools import islice
def fib():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
if __name__ == '__main__':
fib_data = fib()
print(list(islice(fib_data, 0, 10)))
可以看到,使用生成器後,程式碼簡潔了很多!在上述程式碼中新增:
print(type(fib_data))
print(dir(fib_data))
可以看到函式返回的是一個generator物件,且物件實現了迭代器協議。
但是,使用生成器必須要注意的一點是:生成器只能遍歷一次。
什麼時候用生成器呢?
生成器可以使用更少的中間變數來寫流式程式碼, 相比於其它容器物件佔用的記憶體和CPU資源更少一些。當需要一個將返回一個序列或在迴圈中執行的函式時,就可以使用生成器,因為當這些元素被傳遞到另一個函式中進行後續處理時,一次返回一個元素可以有效的提升整體效能,最重要的是,比迭代器簡潔!
除此以外,生成器還有兩個很棒的用處:
- 實現with語句的上下文管理器協議
- 實現協程
什麼是生成器表示式?
列表推導式,大家應該都用到,但是由於記憶體的限制,列表的容量是有限的,如果要建立一個有幾百萬個元素的列表,會佔用很多的儲存空間,當我們只需要訪問幾個元素時,其它元素佔用的空間就白白浪費了。
這種時候你可以用生成器表示式啊,生成式表示式是一種實現生成器的便捷方式,將列表推導式的中括號替換為圓括號,生成器表示式是一種邊迴圈邊計算,使得列表的元素可以在迴圈過程中一個個的推算出來,不需要建立完整的列表,從而節省了大量的空間。
In [1]: a = (item for item in range(10))
In [2]: type(a)
Out[2]: generator
In [3]: next(a)
Out[3]: 0
In [4]: next(a)
Out[4]: 1
以上。
程式碼可參考:my git