Background

最近剛好看到 python Iterator 和 Generator 介紹文章。之前一直只是大概知道,但沒有特別花時間去了解。所以想說今天順便整理並且記錄一下。

iterator

下面是一個基本的 iterator 範例。包含了 __init__, __iter__, __next__。iterator是實現了iterator.iter()和iterator.next()方法的對象。根據官方的說法,正是這個方法,實現了for ... in ...語句。

class MyNumbers:
  def __init__(self):
        self.number = 1
  
  def __iter__(self):
    return self

  def __next__(self):
    self.number += 1
    return self.number

generator

有了iterator的觀念後我們接著來了解 generator。

generator: A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

generator iterator: An object created by a generator funcion.

generator expression: An expression that returns an iterator.

我們常說的生成器,就是帶有 yield 的函數,而 generator iterator 則是 generator function 的返回值,即一個generator對象。yield 和 return 很像,只是當函數呼叫 return 時,該函數 call stack (python 中是 frame) 就會被清除,程式主導權回到呼叫該函數的手上。 而 yield 會把程式主導權交給呼叫該函數的手上,但是他不會把函數的 call stack 清除,因此下次呼叫時,可以從上次未執行的部分開始執行,而不是重新建立一個新 stack。

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

接著我們照下面的流程執行上面的function看結果如何:

>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()
>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

你就能將 yield 理解成一個中斷服務子程序的斷點。所以當我們之後再呼叫 next 時你可以發現是從上一次 yield 的地方開始繼續執行,而不是從頭開始執行,直到程式執行結束。

最後我們可以理解 Generator 的作用其實是實現了懶執行 (lazy evaluation) ,即在真正需要某個值的時候才真正去計算這個值,可以幫助我們去控制對應的情境。

Reference