如今 Python 已經支持用協程進行異步處理。但最近有建議稱添加協程以全面完善 Python 的語言結構,而不是像如今這樣把他們做爲生成器的一個類型。此外,兩個新的關鍵字———異步(async)和等待(await),都該添加到 Python 中來支持協程。html
也許有人不太瞭解協程,其實協程的原理很簡單,打個比方就能講明白了:假設有十我的去食堂打飯,這個食堂比較窮,只有一個打飯窗口和一個打飯阿姨,那麼打飯就只能一個一個排隊進行。這十我的胃口很大,每一個人都要點5個菜,但這十我的都喜歡猶豫不決,點菜的時候每點一個菜後再想下一個菜點什麼,所以後面的人等得很着急呀。python
這樣一直站着也不是個事兒,因此打菜的阿姨看到某人猶豫5秒後就會吼一聲,讓他排到隊伍末尾,讓別人先打菜,等輪到他的時候他也差很少想好吃什麼了。這確實是個不錯的方法,但也有一個缺點,那就是打菜的阿姨會等每一個人5秒鐘,若是那我的在5秒內沒有作出決定吃啥,其實這5秒就浪費了。一我的點一個菜就是浪費5秒,十我的每一個人點5個菜可就浪費的多啦「菜都涼了要」。session
那怎麼辦呢?阿姨又發話了:你們都是學生,學生就要自覺,我之後也不主動讓大家排到末尾了,若是大家以爲本身會猶豫不決,就主動點直接點一個菜就站後面去,等下次排到的時候也差很少想好吃啥了。異步
這個方法果真有效,你們點了菜後想的第一件事情不是下一個菜吃什麼,而是本身會不會猶豫,若是會猶豫那直接排到隊伍後面去,若是不會就接着點菜。這樣一來整個隊伍的效率天然就高了。async
這個例子裏,排隊阿姨的那聲吼就是咱們的 CPU 中斷,用於切換上下文。每一個打飯的學生就是一個 task。而每一個人決定本身要不要讓出窗口的這種行爲,其實就是咱們協程的核心思想。ide
OK,回到主題,協程就是一種能夠在代碼的各類預約義位置暫停和恢復執行的函數,它避免了無心義的調度,由此提升代碼性能。而子程序是一種特殊的協同程序,它只有單一入口,經過回調來完成執行。Python 的協程「現有的以生成器爲基礎的協程和新提出的協程」不是通常意義上的協程,由於在執行暫停時它們只能將控制權轉給調用者,而不是像常見的那樣將控制權轉給別的協程。輔之以事件循環,協程可用於異步處理,尤爲是在 I / O 中。函數
Python 當前支持的協程基於 PEP342 加強型生成器,於 Python 2.5 版本開始採用。該 PEP 將 yield 語句改成表達式,併爲生成器增長了一些新的方法 「 send() , throw() , and close() 」 ,同時確保 close() 方法在生成器進入垃圾回收階段時獲得調用。該功能在 Python 3.3 版本的 PEP 380 中獲得進一步加強,它經過增長 yield 表達式,容許生成器將部分功能授予另外一個生成器「即子生成器」。性能
以上方法都使協程依賴於生成器,這使得在代碼段何處進行異步調用變得使人困惑,且頗受限制。尤爲,with 和 for 聲明在理論上能夠將協程用於異步調用,但 Python 語法在那些位置不容許使用 yield 表達式,所以沒法進行異步調用。此外,若是協程的重構將 yield 或 yield from 從函數中移除 ,它就再也不被視爲協程,這會致使一些不明顯的錯誤; asyncio 模塊經過 @asyncio.coroutine
裝飾器來彌補這方面的不足。fetch
PEP 492 旨在解決以上全部問題。其想法源於 Yury Selivanov 在四月中旬提出的 python-ideas 郵件列表,該想法受到不少人熱情追捧。在5月5日,Guido van Rossum 贊成將它添加在 Python 3.5 版本中。不只如此,5月12日就獲得執行。一切都進展迅速,儘管最終該方法仍是在 python-idea 和 python-dev 方面引發熱情討論。ui
從語法角度看,變化至關簡單:
async def read_data(db): data = await db.fetch('SELECT ...') ...
這個例子「來源於 PEP」將使用新的 async def 構造函數建立一個 read_data()
協程。 await 表達式將暫停執行 read_data()
,直到 db.fetch()
await able
完成並返回其結果。await 相似於 yeild from
,但它會確保其參數 awaitable。
此外還有幾種不一樣類型的 awaitable。一種是本地的協程對象,在調用本地協同程序後的返回爲 awaitable,還有基於生成器且有 @types.coroutine
裝飾的協程。還有一種是將來對象,它表明着在將來完成的操做,也是 awaitable。__await __()
方法在 awaitable 的對象都會出現。
然而,向一種語言添加新的關鍵字時會出現這樣的問題:任何與關鍵字名字相同的變量都會成爲語法錯誤。爲了不該問題,Python 3.5 和 3.6 版本將 「softly deprecate 「 「溫柔棄用」 async 和 wait 爲變量名,而不將他們當作語法錯誤。解析器會跟蹤 async def 塊,並將塊內的關鍵字區別對待,從而使現有的使用繼續有效。
新的特性中,異步還有兩種新用途:異步內容管理器(with)和迭代器(for)。在協程裏,這兩種構造函數的示例以下:
async def commit(session, data): ... async with session.transaction(): ... await session.update(data) ... ... async for row in Cursor(): print(row)
異步內容管理器必須實現兩個異步方法,__aenter __()
和__aexit __()
,他們都返回 awaitables;異步迭代器須實現__aiter __()
和__anext __()
。這些方法都是現有的同步內容管理器和迭代器的異步版本。
此前主要的討論是延期執行的 「cofunction」 功能 PEP 3152 是否會是更好的起點,該 PEP 的做者 Greg Ewing 提出了此問題。但有不少人認爲 Selivanov 提議的語法更適合 codef,cocall ,也有人更加贊同 Ewing 的提議。這樣來來回回的爭論了不少次。有一些人認爲cofunction 的語法在處理某些狀況時至關複雜而且不符合 Python 語言的特性。後來 Van Rossum 總結了 cofunctions 語法存在的問題,並拒絕採納該方法。
此外,還有幾點關於附加異步功能的建議值得討論,但並不緊急。對於關鍵詞的討論有些本末倒置。 await 的優先級問題也討論了一段時間,結果是,不一樣於 yeild 和 yeild from 僅有最低優先級,await 具備較高的優先級。
但 Mark Shannon 抱怨說,實現 Selivanov 的建議並不須要增長新的語法。其餘人也提出了相似的意見,但 Selivanov 或其餘支持者並未對此提出反駁。關鍵在於簡化協同程序的編寫。除此以外,Van Rossum 但願協同程序暫停的位置可以顯而易見,查看代碼就能發現:
新的語法纔是 PEP 存在的意義。我但願經過句法結構就能判斷出協程的懸停點。
在兩三週後,發佈了多個版本的 PEP ,引發了諸多辯論。Selivanov 耐心地解釋他的想法,並根據反饋意見不斷修正本身的想法。異步協程特性對 Python 語言的將來極可能相當重要,整個探索過程都很快,很順遂。不過,Python 開發者們將這些想法付諸實踐極可能還須要一段時間。
原文地址:Python coroutines with async and await
參考文章: 對Python中yield和協程的理解
本文系 OneAPM 工程師翻譯。OneAPM 是應用性能管理領域的新興領軍企業,能幫助企業用戶和開發者輕鬆實現:緩慢的程序代碼和 SQL 語句的實時抓取。想閱讀更多技術文章,請訪問 OneAPM 官方博客。