生成

生成器 在 Python

1 個練習

關於 生成器

generator是一種函式或運算式,會回傳一種特殊的疊代器,稱為生成器疊代器。 Generator-iterators是惰性的:它們不會把values存在記憶體中,而是在需要時才_生成_這些值。

生成器函式看起來和一般函式沒什麼不同,但它包含一個或多個yield 運算式。 每個yield都會暫停程式碼的執行,並保存當下的執行狀態(包含所有區域變數和 try 敘述)。 當生成器恢復執行時,它會接續暫停前的狀態,這點和每次呼叫都會重置的一般函式不同。

建立生成器

生成器的建立方式和其他迴圈或遞迴函式很像,但需要一個yield運算式,我們稍後會深入探討。

舉例來說,一個函式可以回傳某串數字中每個數的_平方_。
以目前的寫法,必須先處理完所有輸入,才能回傳任何值:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

你可以像這樣把該函式改寫成生成器:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

這麼做的理由是,當你不需要_一次_取得所有值時,就會用到生成器。

這能節省記憶體和運算效能,因為只有你_當下正在處理_的值會被計算。

使用生成器

生成器(嚴格來說是generator-iterator,請見下方的注意事項)是一種iterator,在 Python 中只要是需要iterator或iterable的地方都能使用。 這包括需要iterable或iterator作為引數的_函式_或_物件_。 想更深入了解,請參閱如何在 Python 中建立疊代器。

Note

生成器疊代器是疊代器的一個特殊子集。 Iterators是讓你能對_可疊代物件_進行迴圈的機制/協定。 生成器疊代器和常見 Python iterables回傳的疊代器行為非常相似,但有幾個重要差異值得注意:

  • 它們是_惰性求值_的;疊代是_單向_的,無法「退回」先前的值。
  • 疊代回傳的值會_耗盡_它們;不會重置,也不會保存在記憶體中。
  • 它們無法排序,也無法反轉。
  • 它們不是序列型態,也_沒有_indexes。 你無法用加法或減法取得前一個或後一個值,也不能使用中括號([])標記法或切片。
  • 它們不能搭配len()函式使用,因為它們沒有長度。
  • 它們可以是_有限_或_無限_的,從_無限_的generator-iterator收集所有值時要小心!

要使用squares_generator()生成器:

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

生成器中的值也可以透過next()函式來產生或存取。 next()會呼叫生成器物件的__next__()方法,將生成器程式碼「推進」或求值到它的yield運算式,接著這個運算式會「產生」或回傳該值。

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

當generator完全耗盡、沒有更多值可以回傳時,它會擲出StopIteration錯誤。

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

可疊代物件與生成器的差異

生成器是_疊代器_的一個特殊子集。 Iterators是讓你能對_可疊代物件_進行迴圈的機制/協定。 生成器和常見 Python iterables回傳的疊代器行為非常相似,但有幾個重要差異值得注意:

  • 生成器是_單向_的,無法「退回」先前的值。

  • 疊代生成器會消耗回傳的值,不會重置。

  • 生成器(由於是惰性求值)無法排序,也無法反轉。

  • 生成器_沒有_indexes,所以你無法用加法或減法取得前一個或後一個值。

  • 生成器不能搭配len()函式使用。

  • 生成器可以是_有限_或_無限_的,從_無限_生成器收集所有值時要小心。

yield 運算式

yield 運算式和return運算式非常相似。

和return運算式_不同_,yield是在_特定的時間點_把值交給呼叫方,並暫停其餘值的求值或回傳,直到它們被請求為止。

當yield被求值時,它會暫停所屬函式的執行,並回傳該函式_在那個當下_的值。

接著函式會_保持在作用域中_,而當__next__()被呼叫時,執行會繼續進行,直到再次遇到yield。

Note

在函式之外禁止使用yield運算式。

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

為什麼要使用生成器?

生成器在許多應用中都很實用。

處理大型集合時,你可能不會想把它的所有值都放進memory。 生成器可以逐筆處理較大的資料,藉此節省記憶體並提升效能。

當某個流程或計算很_複雜_、很_昂貴_或_無限_時,生成器也非常有幫助:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

現在,每當對infinite_sequence物件呼叫__next__()時,它就會回傳_前一個數字_ + 1。

透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 生成器

練習已鎖定

再解鎖 2 個練習,就能練習 生成器