跳至內容

Python/生成器

維基教科書,自由的教學讀本

生成器(generator)是一種返回一個值的迭代器,每次從該迭代器取下一個值。

生成器表達式(generator expression)類似列表解析(list comprehension)表達式的語法,只不過把列表解析的[]換成(),但是它返回的是一個生成器對象而不是列表對象。

生成器其實是一種特殊的迭代器,不過這種迭代器更加優雅。它不需要迭代器的類一樣寫__iter__()和__next__()方法了,只需要一個yield關鍵字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一種惰性加載的模式生成值。

使用生成器表達式

[編輯]
# 生成器表达式
my_generator = (x for x in range(10))
# 遍历生成器
for item in my_generator:
   print(item)

基於yield的生成器

[編輯]

Python 2.2的PEP 255開始引入生成器。函數如果包含yield指令,該函數調用的返回值是一個生成器對象,此時函數體中的代碼並不會執行,只有顯示或隱示地調用next的時候才會真正執行裏面的代碼。yield可以暫停一個函數並返回此時的中間結果。該函數將保存執行環境並在下一次恢復。

生成器中,如果沒有return語句,則執行到函數完畢時將返回StopIteration異常。如果在執行過程中遇到return語句,則直接拋出StopIteration異常,終止迭代。如果在return後返回一個值,那麼這個值作為StopIteration異常的說明,不是程序的返回值。

可用close()手動關閉生成器函數,後面再調用生成器會直接返回StopIteration異常。

使用 yield 關鍵字創建生成器函數。生成器函數使用 yield 關鍵字來生成值,每次調用生成器的 __next__() 方法時,生成器會從上一次停止的地方繼續執行。

# 生成器函数
def my_generator():
    for x in range(10):
        yield x

# 遍历生成器
for item in my_generator():
    print(item)

生成器函數最大的特點是可以接受通過send()從外部傳入的一個值,並根據該值計算結果後返回。這是生成器函數最難理解的地方,也是最重要的地方,協程的實現依賴於此。語法為

 receive=yield value

其中receive將收到send方法中的參數的值。在啟動生成器函數時只能send(None),如果試圖輸入其它的值都會得到錯誤提示信息。

def cumulative_sum():
    total = 0
    while True:
        value = (yield total)
        if value is None:
            break
        total += value

gen = cumulative_sum()
print(next(gen))  # 输出: 0,启动生成器并得到初始总和

print(gen.send(10))  # 输出: 10,发送10,总和变为10
print(gen.send(20))  # 输出: 30,发送20,总和变为30
print(gen.send(5))   # 输出: 35,发送5,总和变为35

gen.close()  # 关闭生成器

在使用 send() 方法之前,必須先啟動生成器,這通常通過調用 next(gen) 或 gen.send(None) 來實現。生成器初始啟動時,send() 方法只能傳遞 None,因為生成器必須首先運行到 yield 表達式處才會暫停等待。

第一次執行之外的每次執行,首先是在上一次停止的yield之處把傳入值賦給yield語句賦值號左側的變量(如果有的話),然後執行到下一個yield語句並返回其右側表達式(如果有的話)的值。

throw()用來向生成器函數送入一個異常。生成器內部可以捕捉、處理此異常,也可以不處理此異常。

def my_generator():
    try:
        while True:
            yield
    except Exception as e:
        print("Error:", e)

# 使用 throw() 方法向生成器抛出异常
my_generator = my_generator()
next(my_generator)
my_generator.throw(ValueError("Error "))

基於yield from的生成器

[編輯]

Python3.3版本的PEP 380中添加了yield from語法,允許一個生成器將其部分操作委派給另一個生成器。

基於yield的生成器的局限性在於只能向它的直接調用者yield值。例如:

def g(x):
    yield 1
    yield 2

def foo():
    iterator = g(0)
    print(next(iterator))

if __name__ == '__main__':
    next(foo()) # 错误,只能直接调用生成器

這意味着那些包含yield的代碼不能想其他代碼那樣被分離出來放到一個單獨的函數中,讓外界去調用這個函數。這也正是yield from要解決的。yield from為調用者和子生成器之間提供了一個透明的雙向通道,包括從子生成器獲取數據以及向子生成器傳送數據。

雖然yield from主要設計用來向子生成器委派操作任務,但yield from可以向任意的迭代器委派操作。

yield from iterable本質上等於for item in iterable: yield item的縮寫版。例如:

def g(x):
     yield from range(x, 0, -1)
     yield from range(x)

list(g(5)) #结果为[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

委託生成器的作用:在調用方和子生成器之間行成雙向通道。雙向通道的含義:調用方可以通過send()直接發送消息給子生成器,而子生成器yield的值,也是直接返回給調用方。只有子生成器結束(return)了,yield from左邊的變量才會被賦值,委託生成器後面的代碼才會執行。

不同於普通的循環,yield from允許子生成器直接從調用者接收其發送的信息或者拋出調用時遇到的異常,並且返回給委派生產器一個值。如果對子生成器的調用產生StopIteration異常,委派生成器恢復繼續執行yield from後面的語句;若子生成器產生其他任何異常,則都傳遞給委派生成器。如果GeneratorExit異常被拋給委派生成器,或者委派生成產器的close()方法被調用,如果子生成器有close()的話也將被調用。當子生成器結束並拋出異常時,yield from表達式的值是其StopIteration異常中的第一個參數。例如:

def accumulate():    # 子生成器,将传进的非None值累加,传进的值若为None,则返回累加结果
    tally = 0
    while 1:
        next1 = yield
        if next1 is None:
            return tally
        tally += next1

def gather_tallies(tallies):    # 外部生成器,将累加操作任务委托给子生成器
    while 1:
        tally = yield from accumulate()
        tallies.append(tally)

 
tallies = []
acc = gather_tallies(tallies)
next(acc)    # 使累加生成器准备好接收传入值
for i in range(4):
    acc.send(i)

acc.send(None)
for i in range(5):
    acc.send(i)
    
acc.send(None)
print(tallies) #输出[6,10]