首先介紹一下 偏函數
如果需要減少某個函數的參數個數,你可以使用
- functools.partial()
【作用一】:partial() 函數允許你給一個或多個參數設置固定的值,減少接下來被調用時的參數個數。
【作用二】:partial() 用於固定某些參數,並返回一個新的callable對象。
關於協程的調用步驟
下面將簡單介紹asyncio的使用。
-
🌰event_loop 事件循環:程序開啓一個無限的循環,程序員會把一些函數註冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。
-
🌰coroutine 協程:協程對象,指一個使用async關鍵字定義的函數,它的調用不會立即執行函數,而是會返回一個協程對象。協程對象需要註冊到事件循環,由事件循環調用。
-
🌰task 任務:一個協程對象就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含任務的各種狀態。
-
🌰future對象: 代表將來執行或沒有執行的任務的結果。它和task上沒有本質的區別;
-
🌰async/await 關鍵字:python3.5 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的異步調用接口。
重點注意:
1.當我們給一個函數添加了async關鍵字,或者使用asyncio.coroutine裝飾器裝飾,就會把它變成一個異步函數。
2.每個線程有一個事件循環,主線程調用asyncio.get_event_loop時會創建事件循環;
3.將任務封裝爲集合asyncio.gather(*args),之後一起傳入事件循環中;
4.要把異步的任務丟給這個循環的run_until_complete方法,事件循環會安排協同程序的執行。和方法名字一樣,該方法會等待異步的任務完全執行纔會結束。
【註釋】實現協程的不僅僅是asyncio,tornado和gevent都能實現類似的功能。
來看一個實例🌰:
async def test1():
print("1")
async def test2():
print("2")
a = test1()
b = test2()
try:
a.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration as e:
print(e.value)
# 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束,返回值在value中
pass
try:
b.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
print(e.value)
# 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束,返回值在value中
pass
---
輸出:
1
2
【解釋】程序先執行了test1函數,等到test1函數執行完後再執行test2函數。從執行過程上來看目前協程函數與普通函數沒有區別,並沒有實現異步函數。
關於阻塞和await
【概念】使用async
關鍵字定義的協程對象,使用await可以針對耗時的操作進行掛起(是生成器中的yield的替代,但是本地協程函數不允許使用),讓出當前控制權。協程遇到await,事件循環將會掛起該協程,執行別的協程,直到其他協程也掛起
,或者執行完畢,在進行下一個協程的執行。
import asyncio
async def test1():
print("1")
await asyncio.sleep(1) # asyncio.sleep(1)返回的也是一個協程對象
print("2")
async def test2():
print("3")
print("4")
a = test1()
b = test2()
try:
a.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
# 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束
pass
try:
b.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
pass
---
輸出:
1
3
4
【解釋】程序先執行test1協程函數,在執行到await時,test1函數停止了執行(阻塞);接着開始執行test2協程函數,直到test2執行完畢。從結果中,我們可以看到,直到程序運行完畢,test1函數也沒有執行完(沒有執行print(“2”))
【對上面的代碼塊進行修改】,使得test1函數完全執行
import asyncio
async def test1():
print("1")
await test2()
print("2")
async def test2():
print("3")
print("4")
loop = asyncio.get_event_loop()
loop.run_until_complete(test1())
---
輸出:
1
3
4
2
【事件循環方法】asyncio.get_event_loop方法可以創建一個事件循環,然後使用 run_until_complete 將協程註冊到事件循環,並啓動事件循環。
【技能升級】使用async可以定義協程對象,使用await可以針對耗時的操作進行掛起,就像生成器裏的yield一樣,函數讓出控制權。協程遇到await,事件循環將會掛起該協程,執行別的協程,直到其他的協程也掛起或者執行完畢,再進行下一個協程的執行,協程的目的也是讓一些耗時的操作異步化。
關於task任務
由於協程對象不能直接運行,在註冊事件循環的時候,其實是run_until_complete方法將協程包裝成爲了一個任務(task)對象。所謂task對象是Future類的子類,保存了協程運行後的狀態,用於未來獲取協程的結果。我們也可以手動將協程對象定義成task,改進代碼如下:
【對上面的代碼塊再一次修改】
import asyncio
async def test1():
print("1")
await test2()
print("2")
async def test2():
print("3")
print("4")
loop = asyncio.get_event_loop()
task = loop.create_task(test1())
loop.run_until_complete(task)
【重點回顧】前面說到task對象保存了協程運行的狀態,並且可以獲取協程函數運行的返回值;
關於task對象那麼具體該如何獲取呢?
-
需要綁定回調函數
-
直接在運行完task任務後輸出
【提升】如果使用send方法執行函數,則返回值可以通過捕捉StopIteration異常,利用StopIteration.value獲取。
【舉個例子】:🌰
import asyncio
async def test_one():
print("Zurich")
await test_two()
print("Alzacar")
return "stop"
async def test_two():
print("--")
print("$$$")
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(test_one())
loop.run_until_complete(task)
print(task.result())
---
【Output】:
Zurich
Alzacar
--
$$$
使用future對象
【解釋】:future對象有幾個狀態:Pending、Running、Done、Cancelled。創建future的時候,task爲pending,事件循環調用執行的時候當然就是running,調用完畢自然就是done,如果需要停止事件循環,就需要先把task取消,可以使用asyncio.Task獲取事件循環的task。
【程序:關於future對象的使用】
import asyncio
import functools
async def test1():
print("1")
await test2()
print("2")
return "stop"
async def test2():
print("3")
print("4")
def callback(param1,param2,future):
print(param1,param2)
print('Callback:',future.result())
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(test1())
task.add_done_callback(functools.partial(callback,"param1","param2"))# 綁定回調函數
loop.run_until_complete(task)
【說明】通過future對象的result方法可以獲取協程函數的返回值,創建task。test1()是一個協程對象。
回調函數中的future對象就是創建的task對象。
【多插播一句】:回調函數如果需要接受多個參數,可以通過偏函數導入。
【關於協程的停止】
怎麼停止執行協程呢?
【第一步】:需要先取消task
【第二步】:停止loop事件循環。
import asyncio
async def test1():
print("1")
await asyncio.sleep(3)
print("2")
return "stop"
tasks = [
asyncio.ensure_future(test1()),
asyncio.ensure_future(test1()),
asyncio.ensure_future(test1()),
]
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
for task in asyncio.Task.all_tasks():
task.cancel()
loop.stop()
loop.run_forever()
finally:
loop.close()