yield 產生器


你可以在函式中包括yield來「產生」值,表面上看來,yield就像是return會傳回值,但又不中斷函式的執行:
>>> def myrange(n):
...     x = 0
...     while True:
...         yield x
...         x += 1
...         if x == n:
...             break
...
>>> for i in myrange(10):
...     print(i, end='')
... print()
...
0123456789
>>>


上面的程式模擬了內建函式range()的作用。表面上看來,你在myrange()函式中使用yield傳回值,然後執行for in迴圈,接著再使用myrange()傳回下一個值,再執行for in迴圈,就好似myrange()執行過後沒有結束似的。

實際上,在def所定義的本體中,若包括yield運算式,則Python會將之編譯為一個產生器(Generator)。例如:
>>> myrange(10)
<generator object myrange at 0x01C98440>
>>> dir(myrange(10))
['__class__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__get
attribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__',
'__name__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__r
epr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi
_code', 'gi_frame', 'gi_running', 'send', 'throw']
>>>

產生器物件是個具有迭代器(Iterator)介面的物件,也就是說,它具有__next__()方法,可以使用next()函式來取出下一個值,若無法產生下一個值,則會丟出StopIteration物件。例如:
>>> g = myrange(3)
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>


這也就是為何在第一個例子中,在for in迴圈呼叫myrange()會有那樣的結果。一個函式若包括yield,則會傳回產生器物件,而該函式基本上可以包括return,不過不可以指明傳回值(也就是只能傳回None)。return只是用來結束函式的執行流程。例如:
>>> def myrange(n):
...     x = 0
...     while True:
...         yield x
...         return
...
>>> g = myrange(3)
>>> next(g)
0
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

在上例中,第一個next(g)後,函式的執行流程就因return而結束了,嘗試再執行next(g),就函式定義來看,無法再執行到yield運算式,所以就函式定義來看,StopIteration是因為無法執行到yield運算式而丟出的。

先前談過 for 包含式(Comprehension),實際上,for包含式與迭代器都是一個叫產生器運算式的語言特性。在 for 包含式(Comprehension) 中最後一個例子也有提到,使用()與for包含式時,實際上是建立一個產生器。例如:
>>> (i ** 2 for i in range(3))
<generator object <genexpr> at 0x01C98440>
>>> g = (i ** 2 for i in range(3))
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> for p in (i ** 2 for i in range(3)):
...     print(p)
...
0
1
4
>>>


從Python 2.5開始,yield從陳述改為運算式,也就是yield除了「產生」指定的值之外,會有一個運算結果,yield運算結果預設是None,你可以透過產生器的send()方法傳入一個值,這個值就成為yield的運算結果。這給了你一個與產生器溝通的機會。例如:
>>> def myrange(n):
...     x = 0
...     while True:
...         val = (yield x)
...         if val is not None:
...             x = val
...         else:
...             x += 1
...         if x >= n:
...             break
...
>>> g = myrange(10)
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2
>>> g.send(0)
0
>>> next(g)
1
>>> next(g)
2
>>> g.send(5)
5
>>> next(g)
6
>>> next(g)
7
>>>