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__方法進行迭代。

那么,什么是迭代器?

實現了迭代器協議的對象就是迭代器,所謂的迭代器協議可以簡單歸納為:

  1. 實現__iter__()方法,返回一個迭代器
  2. 實現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資源更少一些。當需要一個將返回一個序列或在循環中執行的函數時,就可以使用生成器,因為當這些元素被傳遞到另一個函數中進行后續處理時,一次返回一個元素可以有效的提升整體性能,最重要的是,比迭代器簡潔!

除此以外,生成器還有兩個很棒的用處:

  1. 實現with語句的上下文管理器協議
  2. 實現協程

什么是生成器表達式?

列表推導式,大家應該都用到,但是由於內存的限制,列表的容量是有限的,如果要創建一個有幾百萬個元素的列表,會占用很多的儲存空間,當我們只需要訪問幾個元素時,其它元素占用的空間就白白浪費了。

這種時候你可以用生成器表達式啊,生成式表達式是一種實現生成器的便捷方式,將列表推導式的中括號替換為圓括號,生成器表達式是一種邊循環邊計算,使得列表的元素可以在循環過程中一個個的推算出來,不需要創建完整的列表,從而節省了大量的空間。

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 github


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM