生成器進化到協程 Part 1

前言

這篇文章大部分來自 David Beazley 在 PyCon 2014 的 PPT 《Generators: The Final Frontier》。這個PPT很長並且很是燒腦,建議在閱讀前應瞭解 Python 的生成器與攜程相關知識,推薦《流暢的 Python》。python

生成器(generator)

使用 yield 來定義一個生成器app

def countdown(n):
    while n > 0:
        yield n
        n -= 1
    
c = countdown(10)
c
<generator object countdown at 0x0000021F5EAB9F10>

生成器可用於迭代函數

for x in countdown(10):
    print('倒數:', x)
倒數: 10
倒數: 9
倒數: 8
倒數: 7
倒數: 6
倒數: 5
倒數: 4
倒數: 3
倒數: 2
倒數: 1

能夠經過 next() 來一步步地讓生成器 yield 一個值,直到函數迭代器結束並拋出 StopIteration。若是你對這一頭霧水,建議閱讀《Fluent Python》14.4 章。
這裏 for 其實至關於不斷地調用 next 並處理 StopIteration命令行

c = countdown(3)

# next(c)
3
# next(c)
2
# next(c)
1

把生成器看成管道

你能夠嵌套生成器,這會致使相似於 Unix 命令行管道的效果code

def add_A(seq):
    for item in seq:
        yield item + '-A'

def add_B(seq):
    for item in seq:
        yield item + '-B'

def add_C(seq):
    for item in seq:
        yield item + '-C'
        
seq = ['apple', 'banana', 'orange']

stacked_generator = add_C(add_B(add_A(seq)))

for item in stacked_generator:
    print(item)
apple-A-B-C
banana-A-B-C
orange-A-B-C

能夠看到,咱們爲 seq 裏的每項都依次添加了一個 tag。get

yield 能夠接受傳值

yield 的做用是向調用者返回一個值,調用者其實也能夠向生成器傳值。generator

def receiver():
    while True:
        received_item = yield
        print('收到:', received_item)

def caller():
    recv = receiver()
    next(recv) # 使生成器前進到 yield
    for i in 'abcd':
        recv.send(i)
        
caller()
收到: a
收到: b
收到: c
收到: d

send 函數的返回值是什麼呢?博客

def receiver():
    call_times = 0
    while True:
        item = yield call_times
        print('收到:', item)
        call_times += 1
        
def caller():
    recv = receiver()
    next(recv)
    for i in 'abcd':
        ret_value = recv.send(i)
        print('返回值: ', ret_value)
        
caller()
收到: a
返回值:  1
收到: b
返回值:  2
收到: c
返回值:  3
收到: d
返回值:  4

因此 send 能夠向生成器傳值的同時會讓生成器前進到下一個 yield 語句,並將 yield 右側的值做爲返回值。it

生成器 101

  • yield 用於定義生成器函數
  • 只要 yield 存在該函數一定是一個生成器
  • 調用該函數返回一個生成器

讓一個生成器前進

使用 next 使一個生成器前進到下一個 yield 語句處,並將產出值(yielded value)做爲其返回值。使用 gen.__next__()效果相同。io

注意:這是一個新建立的生成器惟一容許的操做。

def generator():
    yield 'a'
    yield 'b'
    
gen = generator()

# next(gen)
'a'
# next(gen)
'b'

給生成器傳值

能夠經過調用生成器的 send 方法來向生成器傳值,這將讓生成器從上次暫停的 yield 前進到下個 yield,並將產出值做爲 send 的返回值。

def generator():
    item = yield 'a'
    print(item)
    another_item = yield 'b'
    
gen = generator()
print(next(gen))
print(gen.send(1))
a
1
b

關閉一個生成器

經過調用生成器 close 方法能夠生成器在 yield 語句處拋出 GeneratorExit。這時僅容許 return,若是沒有捕捉這個錯誤,生成器會靜默關閉,不拋出錯誤。

def generator():
    times = 0
    while True:
        yield times
        times += 1
            
gen = generator()
print(next(gen))
print(next(gen))
gen.close() # 不會拋出錯誤
0
1

拋出錯誤

調用生成器的 throw 方法能夠在 yield 處拋出某個特定類型的錯誤,若是生成器內部能夠捕捉這個錯誤,那生成器將前進到下個 yield 語句處,並將產出值做爲 throw 的返回值,不然停止生成器。
throw 的函數簽名以下:

throw(typ, [,val, [,tb]])

其中 tyb 是某錯誤類型,val是錯誤信息,tb 爲 traceback。更多信息能夠參考官方的PEP0342

def generator():
    try:
        yield 'apple'
    except RuntimeError as e:
        print('捕捉到:', e)
    yield 'banana'
    

gen = generator()
print(next(gen))
print(gen.throw(RuntimeError, '運行錯誤'))
apple
捕捉到: 運行錯誤
banana

生成器返回值

若是在生成器函數中加上 return 那在運行到 return 時將會把返回值做爲 StopIteration 的值傳遞出去。這個是 Python3 的特性,Python2 生成器不能返回某個值。

def generator():
    yield
    return 'apple'
    
g = generator()
next(g)
try:
    next(g)
except StopIteration as e:
    print(e)
apple

生成器委託

使用 yield from 能夠幫你對一個生成器不斷調用 next 函數,並返回生成器的返回值。言下之意是你能夠在生成器裏調用生成器

def generator():
    yield 'a'
    yield 'b'
    return 'c'
    
def func():
    result = yield from generator()
    print(result)

調用 func 結果是返回一個生成器

# func()
<generator object func at 0x0000021F5EB0F990>

# next(func())
'a'

另一個例子

def chain(x, y):
    yield from x
    yield from y
    
a = [1, 2, 3]
b = [4, 5, 6]

for i in chain(a, b):
    print(i, end=' ')
1 2 3 4 5 6
c = [7, 8, 9]
for i in chain(a, chain(b, c)):
    print(i, end=' ')
1 2 3 4 5 6 7 8 9

Part 1總結

生成器定義

def generator():
    ...
    yield
    ...
    return result

生成器操做

gen = generator()

# 使一個生成器前進
next(gen)

# 傳遞一個值
gen.send(item)

# 停止生成器
gen.close()

# 拋出錯誤
gen.throw(exc, val, tb)

# 委託
result = yield from gen
相關文章
相關標籤/搜索