| >>> 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 >>> |
沒有留言:
張貼留言