標籤:

Python generators, coroutines, native coroutines and async/await

Abstraction is not about vagueness, it is about being precise at a new semantic level. - Dijkstra

筆者之前學習python的時候就對這幾個概念有些困惑,尤其是python3之後又不斷添加了yield from, async, await等關鍵字用來支持非同步編程。最近看到一篇比較好的博客就結合自己的理解翻譯並解釋一下這些概念,包括生成器,協程,原生協程和python3.5引入的async/await。 請使用python3.5運行代碼示例。

Generators(生成器)

python中生成器是用來生成值的函數。通常函數使用return返回值然後作用域被銷毀,再次調用函數會重新執行。但是生成器可以yield一個值之後暫停函數執行,然後控制權交給調用者,之後我們可以恢復其執行並且獲取下一個值,我們看一個例子:

def simple_gen():n yield hellon yield worldnngen = simple_gen()nprint(type(gen)) # <class generator>nprint(next(gen)) # hellonprint(next(gen)) # worldn

注意生成器函數調用的時候不會直接返回值,而是返回一個類似於可迭代對象(iterable)的生成器對象(generator object),我們可以對生成器對象調用next()函數來迭代值,或者使用for循環。

生成器常用來節省內存,比如我們可以使用生成器函數yield值來替代返回一個耗費內存的大序列:

def f(n):n res = []n for i in range(n):n res.append(i)n return resnndef yield_n(n):n for i in range(n):n yield in

Coroutines(協程)

上一節講到了使用使用生成器來從函數中獲取數據(pull data),但是如果我們想發送一些數據呢(push data)?這時候協程就發揮作用了。yield關鍵字既可以用來獲取數據也可以在函數中作為表達式(在=右邊的時候)。我們可以對生成器對象使用send()方法來給函數發送值。這叫做『基於生成器的協程』(generator based coroutines),下邊是一個示例:

def coro():n hello = yield hello # yield關鍵字在=右邊作為表達式,可以被send值n yield hellonnnc = coro()nprint(next(c)) # 輸出 hellonprint(c.send(world)) # 輸出 worldn

這裡發生了什麼?和之前一樣我們先調用了next()函數,代碼執行到yield hello然後我們得到了』hello』之後我們使用了send函數發送了一個值』world』, 它使coro恢復執行並且賦了參數』world』給hello這個變數,接著執行到下一行的yield語句並將hello變數的值』world』返回。所以我們得到了send()方法的返回值』world』。

當我們使用基於生成器的協程(generator based coroutines)時候,術語」generator」和」coroutine」通常表示一個東西,儘管實際上不是。而python3.5以後增加了async/await關鍵字用來支持原生協程(native coroutines),我們在後邊討論。

Async I/O and the asyncio module (非同步IO和asyncio模塊)

python3.4以後標準庫增加了新的asyncio模塊來支持更加簡潔的非同步編程。我們可以在asyncio模塊使用協程輕鬆實現非同步IO,下邊是一個來自官方文檔的示例:

import asyncionimport datetimenimport randomnnn@asyncio.coroutinendef display_date(num, loop):n end_time = loop.time() + 50.0n while True:n print(Loop: {} Time: {}.format(num, datetime.datetime.now()))n if (loop.time() + 1.0) >= end_time:n breakn yield from asyncio.sleep(random.randint(0, 5))nnnloop = asyncio.get_event_loop()nasyncio.ensure_future(display_date(1, loop))nasyncio.ensure_future(display_date(2, loop))nloop.run_forever()n

我們創建了一個協程display_date(num, loop),它接收一個數字和event loop作為參數,然後持續輸出當前時間。然後使用yield from關鍵字來await從asyncio.sleep()執行的結果。asyncio.sleep()是一個協程,在指定時間以後完成。之後我們在默認的事件循環(event loop)中使用asyncio.ensure_future()來調度協程的執行,最後通知事件循環一直執行下去。

如果我們執行這段代碼,可以看到兩個協程是並發執行的。當我們用yield from的時候,事件循環知道它將(這裡指sleep函數)將會忙碌一段時間然後暫停這個協程的執行轉而執行另一個協程。所以這兩個協程能夠並發執行(注意並發不是並行,因為event loop是單線程的,所以不是真正意義上的『同時執行』)。

這裡只需要知道yield from是一個語法糖用來替代下邊這種形式的寫法,這種形式使代碼更加簡潔。

# yield from 等價方式 yield from asyncio.sleep(random.randint(0, 5))nfor x in asyncio.sleep(random.randint(0, 5)):n yield xn

Native Coroutines and async/await (原生協程與async/await)

記住到目前為止,我們仍然使用的是 基於生成器的協程(generators based coroutines),在python3.5中,python增加了使用async/await語法的原生協程(native coroutines)。之前的函數用async/await語法可以這麼寫:

import asyncionimport datetimenimport randomnnasync def display_date(num, loop):n end_time = loop.time() + 50.0n while True:n print(Loop: {} Time: {}.format(num, datetime.datetime.now()))n if (loop.time() + 1.0) >= end_time:n breakn await asyncio.sleep(random.randint(0, 5))nnnloop = asyncio.get_event_loop()nasyncio.ensure_future(display_date(1, loop))nasyncio.ensure_future(display_date(2, loop))nloop.run_forever()n

你能看出變化嗎?實際上就是去掉了裝飾器@asyncio.coroutine,然後在定義前加上async關鍵字,之後把yield from替換成await。寫法是不是更加簡潔了?

Native vs Generator Based Coroutines: Interoperability (原生協程 vs 基於生成器的協程)

實際上除了語法之外原生協程(async/await)和基於生成器的協程(@asyncio.coroutine/yield from)並沒有功能上的區別。但是注意,這兩種寫法不能混用,就是說你不能在generator based coroutines里使用await,或者在naive coroutines裡頭使用yield或者yield from。

除此之外,兩種寫法是互通的,我們可以同時使用,比如我們可以在原生協程里await一個基於生成器的協程,也可以在基於生成器的協程里yield from一個使用async定義的原生協程。

比如我們同時在一個時間循環里使用兩種協程:

import asyncionimport datetimenimport randomnimport typesnnn@types.coroutinendef my_sleep_func():n yield from asyncio.sleep(random.randint(0, 5)) # 注意這裡就不能用 awaitnnnasync def display_date(num, loop):n end_time = loop.time() + 50.0n while True:n print(Loop: {} Time: {}.format(num, datetime.datetime.now()))n if (loop.time() + 1.0) >= end_time:n breakn await my_sleep_func() # 注意這裡就不能用 asyncnnnloop = asyncio.get_event_loop()nasyncio.ensure_future(display_date(1, loop))nasyncio.ensure_future(display_date(2, loop))nloop.run_forever()n

Ref

python: generators, coroutines, native coroutines and async/await

How the heck does async/await work in Python 3.5?


推薦閱讀:

Python黑帽編程 3.5 DTP攻擊
linux如何高效的學習語言編程?
結巴分詞獲取關鍵詞時怎麼過濾掉一些停用詞?
一個通用爬蟲思路(Python3)
手撕KNN

TAG:Python |