任何使用yield語句的函數都稱為生成器。調用生成器函數將創建一個對象,該對象通過連續調用next()方法(在python3中是__next__())生成結果序列。
next()調用使生成器函數一直運行到下一條yield語句為止。此時next()將返回值傳遞給yield,而且函數將暫時中止執行。再次調用next()時,函數將繼續執行yield之后的語句。此過程持續到函數返回為止。
通常不會在生成器上直接調用next()方法,而是在for語句、sum()或一些使用序列的其他操作中使用它。
生成器函數完成的標志是返回或引發StopIteration異常,這標志着迭代的結束。如果生成器在完成時返回None之外的值,都是不合法的。
生成器是由兩部分組成:生成器的函數和生成器的迭代器。
生成器的函數是用def 語句定義的,包含yield的部分;生成器的迭代器是這個函數返回的部分。按一種不是很准確的說法,兩個實體經常被當作一個,合起來叫做生成器。
>>> def simple_generator(): ... yield 1 ... >>> simple_generator <function simple_generator at 0x16eb398> >>> simple_generator() <generator object simple_generator at 0x16cfc30> >>>
生成器的函數返回的迭代器可以像其他迭代器那樣使用。
首先請確信,生成器就是一種迭代器。生成器擁有next方法並且行為與迭代器完全相同,這意味着生成器也可以用於Python的for循環中。另外,對於生成器的特殊語法支持使得編寫一個生成器比自定義一個常規的迭代器要簡單不少,所以生成器也是最常用到的特性之一。
從Python 2.5開始,[PEP 342:通過增強生成器實現協同程序]的實現為生成器加入了更多的特性,這意味着生成器還可以完成更多的工作。這部分我們會在稍后的部分介紹。
生成器函數
使用生成器函數定義生成器
如何獲取一個生成器?首先來看一小段代碼:
1
2
3
4
5
6
7
|
>>>
def
get_0_1_2():
...
yield
0
...
yield
1
...
yield
2
...
>>> get_0_1_2
<function get_0_1_2 at
0x00B2CB70
>
|
我們定義了一個函數get_0_1_2,並且可以查看到這確實是函數類型。但與一般的函數不同的是,get_0_1_2的函數體內使用了關鍵字yield,這使得get_0_1_2成為了一個生成器函數。生成器函數的特性如下:
- 調用生成器函數將返回一個生成器;
123
>>> generator
=
get_0_1_2()
>>> generator
<generator
object
get_0_1_2 at
0x00B1C7D8
>
- 第一次調用生成器的next方法時,生成器才開始執行生成器函數(而不是構建生成器時),直到遇到yield時暫停執行(掛起),並且yield的參數將作為此次next方法的返回值;
12
>>> generator.
next
()
0
- 之后每次調用生成器的next方法,生成器將從上次暫停執行的位置恢復執行生成器函數,直到再次遇到yield時暫停,並且同樣的,yield的參數將作為next方法的返回值;
1234
>>> generator.
next
()
1
>>> generator.
next
()
2
- 如果當調用next方法時生成器函數結束(遇到空的return語句或是到達函數體末尾),則這次next方法的調用將拋出StopIteration異常(即for循環的終止條件);
1234
>>> generator.
next
()
Traceback (most recent call last):
File
"<stdin>"
, line
1
,
in
<module>
StopIteration
- 生成器函數在每次暫停執行時,函數體內的所有變量都將被封存(freeze)在生成器中,並將在恢復執行時還原,並且類似於閉包,即使是同一個生成器函數返回的生成器,封存的變量也是互相獨立的。
我們的小例子中並沒有用到變量,所以這里另外定義一個生成器來展示這個特點:12345678910111213>>>
def
fibonacci():
... a
=
b
=
1
...
yield
a
...
yield
b
...
while
True
:
... a, b
=
b, a
+
b
...
yield
b
...
>>>
for
num
in
fibonacci():
...
if
num >
100
:
break
...
print
num,
...
1
1
2
3
5
8
13
21
34
55
89
生成器注意事項:
既然生成器函數也是函數,那么它可以使用return輸出返回值嗎?
不行的親,是這樣的,生成器函數已經有默認的返回值——生成器了,你不能再另外給一個返回值;對,即使是return None也不行。但是它可以使用空的return語句結束。如果你堅持要為它指定返回值,那么Python將在定義的位置贈送一個語法錯誤異常,就像這樣:
>>> def i_wanna_return(): ... yield None ... return None ... File "<stdin>", line 3 SyntaxError: 'return' with argument inside generator
- 如果我需要在生成器的迭代過程中接入另一個生成器的迭代怎么辦?寫成下面這樣好傻好天真。。
12345
>>>
def
sub_generator():
...
yield
1
...
yield
2
...
for
val
in
counter(
10
):
yield
val
...
12345678>>>
def
sub_generator():
...
yield
1
...
yield
2
...
yield
from
counter(
10
)
File
"<stdin>"
, line
4
yield
from
counter(
10
)
^
SyntaxError: invalid syntax
任務
序列中的子項可能是序列,子序列的子項仍可能是序列,以此類推,則序列嵌套可以達到任意的深度。需要循環遍歷一個序列,將其中所有的子序列展開成一個單一的、只具有基本子項的序列。(一個基本子項或者原子,可以是任何非序列的對象-或者說葉子,假如你認為嵌套序列是一棵樹。)
解決方案
我們需要能夠判斷哪些我們正在處理的子項是需要被展開的,哪些是原子。為了獲得通用性,我們使用了一個斷定來作為參數,由它來判斷子項是否是可以展開的。(斷定[predicate]是一個函數,每當我們處理一個元素時就將其應用於該元素並返回一個布爾值:在這里,如果元素是一個需要展開的子序列就返回True,否則返回False。)我們假定每一個列表或者元組都是需要被展開的,而其他類型則不是。那么最簡單的解決方法就是提供一個遞歸的生成器:
def list_or_tuple(x): return isinstance(x, (list, tuple)) def flatten(sequence, to_expand=list_or_tuple): for item in sequence: if to_expand(item): for subitem in flatten(item, to_expand): yield subitem else: yield item
- for x in flatten([1, 2, [3, [ ], 4, [5, 6], 7, [8,], ], 9]):
- print x,
- 輸出1 2 3 4 5 6 7 8 9。
我們也可以寫一個非遞歸版本的flatten。這種寫法可以超越Python的遞歸層次的極限,一般不超過幾千層。實現無遞歸遍歷的要點是,采用一個明確的后進先出(LIFO)棧。在這個例子中,我們可以用迭代器的列表實現:
def flatten(sequence, to_expand=list_or_tuple): iterators = [ iter(sequence) ] while iterators: # 循環當前的最深嵌套(最后)的迭代器 for item in iterators[-1]: if to_expand(item): # 找到了子序列,循環子序列的迭代器 iterators.append(iter(item)) break else: yield item else: # 最深嵌套的迭代器耗盡,回過頭來循環它的父迭代器 iterators.pop( )
轉自:http://book.51cto.com/art/201005/198629.htm