python中的yield、yield from、async/await中的區別與聯絡
因為水平有限,我在看asyncio非同步爬蟲的時候,關於async/await的相關知識,看了挺多資料,還是不得其要領,所以用yield、yields from到async\await從頭捋搞了一遍,發現裡邊得東西還是挺有意思,所以搞了這麼一篇文章。
0.yield
yield
可以將一個函式轉換成generator(生成器)
,和普通函式不同得是,生成一個generator
看起來像函式呼叫,但不會執行任何函式程式碼,直到對其呼叫next()
/send()
才開始執行,雖然執行流程仍按函式得流程執行,但是執行到每一個yield
語句就會中斷,交出執行權,並返回一個迭代值,下次執行從yield
中斷處可以通過send()
接收一個引數並繼續執行。看起來就像一個函式在正常執行得過程中被yield
中斷了數次,每次中斷都會交出執行權,通過yield
返回當前的迭代值。
yield
的好處是顯而易見的,把一個函式改寫為一個generator
就獲得了迭代能力,這也是python中協程的基礎。
0.1 next()
next()
的作用是每次獲取一個yield出來的值。
def generator(): yield 1 yield 2 yield 3 return 4 if __name__ == "__main__": gen = generator() print(next(gen)) print(next(gen)) print(next(gen)) print(next(gen))# 因為不是yield,這裡丟擲一個StopIteration的錯誤,但錯誤資訊中會返回 4。 錯誤詳情:StopIteration: 4 gen.close()
0.2 send()
send()
和next()
類似,但是比next()多一個功能,可以往生成器中傳入引數。
def generator2(): num = yield 1 print (num) yield 2 yield 3 return 4 if __name__ == "__main__": gen = generator2() print(gen.send(None))# 如果使用send()的時候,第一個傳入的值必須是None,因為當呼叫send()的時候,才執行到第一個yield,只能輸出一個值,不能接收值。 print(gen.send(101)) print(gen.send(None)) gen.close()
注:關於StopIteration:1. 在一個生成器中,如果沒有return,則預設執行到函式完畢時返回StopIteration;2. 如果遇到return,如果在執行過程中 return,則直接丟擲 StopIteration 終止迭代。3.在close()之後,如果再執行相關的操作,會丟擲StopIteration;
0.3 throw()
throw()
用來向生成器函式送入一個異常,可以結束系統定義的異常,或者自定義的異常。我的理解是相當於send()進去一個錯誤型別,然後丟擲異常。我的理解可能不準確,僅供參考。
def generator3(): num = yield 1 print(num) try: yield 2 except ValueError: print("捕獲異常:ValueError") yield 3 if __name__ == "__main__": gen = generator3() print(gen.send(None)) print(gen.send(101)) gen.throw(ValueError) gen.close()
1.yield from
我們先來看個例子:
def test03(iterabld): yield iterabld def test04(iterable): yield from iterable if __name__ == "__main__": for iterabld in test03(range(5)): print(iterabld) for value in test04(range(5)): print (value) 返回值: # range(0, 5) # 0 # 1 # 2 # 3 # 4
這兩個的返回值是一樣的,可以看的出來,yield form 將可迭代物件中的值直接迭代出來了。下邊再看一個例子:
def generator01(): num = yield 1 print(num) yield 2 yield 3 return 4 def test01(gen): ll = yield from gen() print(ll) def main(): gen = test01(generator01) print(gen.send(None)) print(gen.send(101)) print(gen.throw(ValueError)) if __name__ == "__main__": main()
執行下例子,可以發現,再main()中的send()和throw()都是直接作用
到生成器generator01中的。這其中main()函式被稱作呼叫方
,test01()被稱作委託生成器
(包含yield from表示式的生成器函式),generator01()被稱作子生成器
(yield from後面加的生成器函式)。
委託生成器的作用是:在呼叫方與子生成器之間建立一個雙向通道
,拿例子來講,就是main()中的send()等直接作用到generator01到,而generator01中yield產生的值直接返回到main()中。
委託生成器中:ll = yield from gen()
為啥會賦值? 這是接收的return的值。生成器沒有yield,有return的時候,會丟擲StopIteration異常,在丟擲StopIteration的異常的時候,會將return的值賦給ll。
注:1. yield from 是在Python3.3才出現的語法。所以這個特性在Python2中是沒有的。 2.yield from 後面需要加的是可迭代物件。它可以是普通的可迭代物件,也可以是迭代器、生成器。
2. async/await
async/await 是用是python3.5後出來的協程非同步程式設計的API, 是為了區分yield,yield from生成器,而使語義更加明確。
以下示例程式碼只是為了展示await與yield from對比,實際開發中,不要這麼做!不要這麼做!!!
# 示例程式碼一 import requests async def request(url): return requests.get(url) async def spider(url): return await request(url) if __name__ == '__main__': sp = spider("http://www.baidu.com") try: print(sp.send(None)) except StopIteration as e: print (e.value) # 示例程式碼二 import requests import types @types.coroutine def request(url): yield requests.get(url) async def spider(url): return await request(url) if __name__ == '__main__': sp = spider("http://www.baidu.com") print(sp.send(None)) # 以上程式碼的列印結果都是 <Response [200]>
async def
語法定義協程函式
,在之前這個功能是通過裝飾器實現的。但是這樣定義的協程函式中不能使用yield語句
,只允許使用return或await語句返回資料。
await
的使用場景與yield from
類似,但是await
接收的物件不同。yield from
可以是任意的可迭代物件。而await
接收的物件必須是可等待物件(awaitable object)
注:1. async/await是在python3.5版麼以及之後的版本中才能使用。2. async不能和yield同時使用。3.await只能作用於可等待物件
推薦閱讀:https://www.cnblogs.com/harelion/p/8496360.html
個人的感想:async/await的出現是為了協程,是為了區分生成器使程式設計更加明確,來提升Python中的非同步程式設計體驗。具體的使用將在接下來的asyncio的介紹中,將大量使用async/await。