python協程函數(python協方差函數)

本文目錄一覽:

如何用python寫一個協程

我學習了asyncio的協程,現在在我的印象中一個協程有兩個要素:

* 用`asyncio.coroutine`裝飾

* 用`yield from`調用其他協程

我想要了解協程是什麼,所以做了以下嘗試。

我經過嘗試,發現運行構造出來的協程得到的是一個`generator`(迭代器)。

而最常規的迭代器生成使用的是`yield`。

所以同樣是生成迭代器,那協程是否可以用`yield`而不是`yield from`。

我經過嘗試,發現協程的調用有特殊的方式。

而最常規的迭代器都是直接調用就可以的。

所以,同樣是函數,那協程是否可以脫離`event_loop`(消息循環)調用。

我還嘗試過通過`yield`構造一個協程。

沒有報錯也運行成功了,所以應該沒有問題。

python 中的協程是怎麼實現多任務的?

協程也稱為微線程,是在一個線程中,通過不斷的切換任務函數實現了多任務的效果。

協程在python實現的原理主要是通過yield這個關鍵字實現

但是真正在開發時,可以不需要自己實現,可以通過很多成熟的第三方模塊來實現協程,比如greenlet,gevent等模塊。多線程的課程我記得是在黑馬程序員裏面找的,一套,還有資料。

Python協程之asyncio

asyncio 是 Python 中的異步IO庫,用來編寫並發協程,適用於IO阻塞且需要大量並發的場景,例如爬蟲、文件讀寫。

asyncio 在 Python3.4 被引入,經過幾個版本的迭代,特性、語法糖均有了不同程度的改進,這也使得不同版本的 Python 在 asyncio 的用法上各不相同,顯得有些雜亂,以前使用的時候也是本着能用就行的原則,在寫法上走了一些彎路,現在對 Python3.7+ 和 Python3.6 中 asyncio 的用法做一個梳理,以便以後能更好的使用。

協程,又稱微線程,它不被操作系統內核所管理,而完全是由程序控制,協程切換花銷小,因而有更高的性能。

協程可以比作子程序,不同的是,執行過程中協程可以掛起當前狀態,轉而執行其他協程,在適當的時候返回來接着執行,協程間的切換不需要涉及任何系統調用或任何阻塞調用,完全由協程調度器進行調度。

Python 中以 asyncio 為依賴,使用 async/await 語法進行協程的創建和使用,如下 async 語法創建一個協程函數:

在協程中除了普通函數的功能外最主要的作用就是:使用 await 語法等待另一個協程結束,這將掛起當前協程,直到另一個協程產生結果再繼續執行:

asyncio.sleep() 是 asyncio 包內置的協程函數,這裡模擬耗時的IO操作,上面這個協程執行到這一句會掛起當前協程而去執行其他協程,直到sleep結束,當有多個協程任務時,這種切換會讓它們的IO操作並行處理。

注意,執行一個協程函數並不會真正的運行它,而是會返回一個協程對象,要使協程真正的運行,需要將它們加入到事件循環中運行,官方建議 asyncio 程序應當有一個主入口協程,用來管理所有其他的協程任務:

在 Python3.7+ 中,運行這個 asyncio 程序只需要一句: asyncio.run(main()) ,而在 Python3.6 中,需要手動獲取事件循環並加入協程任務:

事件循環就是一個循環隊列,對其中的協程進行調度執行,當把一個協程加入循環,這個協程創建的其他協程都會自動加入到當前事件循環中。

其實協程對象也不是直接運行,而是被封裝成一個個待執行的 Task ,大多數情況下 asyncio 會幫我們進行封裝,我們也可以提前自行封裝 Task 來獲得對協程更多的控制權,注意,封裝 Task 需要 當前線程有正在運行的事件循環 ,否則將引 RuntimeError,這也就是官方建議使用主入口協程的原因,如果在主入口協程之外創建任務就需要先手動獲取事件循環然後使用底層方法 loop.create_task() ,而在主入口協程之內是一定有正在運行的循環的。任務創建後便有了狀態,可以查看運行情況,查看結果,取消任務等:

asyncio.create_task() 是 Python3.7 加入的高層級API,在 Python3.6,需要使用低層級API asyncio.ensure_future() 來創建 Future,Future 也是一個管理協程運行狀態的對象,與 Task 沒有本質上的區別。

通常,一個含有一系列並發協程的程序寫法如下(Python3.7+):

並發運行多個協程任務的關鍵就是 asyncio.gather(*tasks) ,它接受多個協程任務並將它們加入到事件循環,所有任務都運行完成後會返回結果列表,這裡我們也沒有手動封裝 Task,因為 gather 函數會自動封裝。

並發運行還有另一個方法 asyncio.wait(tasks) ,它們的區別是:

在Python中使用Asyncio系統(3-4)Task 和 Future

Task 和 Future

前面我們討論了協程,以及如何在循環中運行它們才有用。現在我想簡單談談Task和Future api。你將使用最多的是Task,因為你的大部分工作將涉及使用create_task()函數運行協程,就像在第22頁的「快速開始」中設置的那樣。Future類實際上是Task的超類,它提供了與循環交互操作的所有功能。

可以這樣簡單地理解:Future表示某個活動的未來完成狀態,並由循環管理。Task是完全相同的,但是具體的「activity」是一個協程——可能是你用async def函數加上create_task()創建的協程。

Future類表示與循環交互的某個東西的狀態。這個描述太模糊了,不太有用,所以你可以將Future實例視為一個切換器,一個完成狀態的切換器。當創建Future實例時,切換設置為「尚未完成」狀態,但稍後它將是「完成」狀態。事實上,Future實例有一個名為done()的方法,它允許你檢查狀態,如示例 3-15所示。

示例 3-15. 用done()方法檢查完成狀態

Future實例還可以執行以下操作:

• 設置一個result值(用.set_result(value)設置值並且使用 .result()獲取值)

• 使用.cancel()方法取消 (並且會用使用.cancelled()檢查是否取消)

• 增加一個Future完成時回調的函數

即使Task更常見,也不可能完全避免使用Future:例如,在執行器上運行函數將返回Future實例,而不是Task。讓我們快速看一下 示例 3-16 ,了解一下直接使用Future實例是什麼感覺。

示例 3-16. 與Future實例的交互

(L3)創建一個簡單的 main函數。我們運行這個函數,等上一會兒然後在Future f上設置一個結果。

(L5)設置一個結果。

(L8)手動創建一個Future實例。注意,這個實例(默認情況下)綁定到我們的循環,但它沒有也不會被附加到任何協程(這就是Tasks的作用)。

(L9)在做任何事情之前,確認future還沒有完成。

(L11)安排main()協程,傳遞future。請記住,main()協程所做的所有工作就是sleep,然後切換Future實例。(注意main()協程還不會開始運行:協程只在事件循環運行時才開始運行。)

(L13)在這裡我們在Future實例上而不是Task實例上使用run_until_complete()。這和你以前見過的不一樣。現在循環正在運行,main()協程將開始執行.

(L16)最終,當future的結果被設置時,它就完成了。完成後,可以訪問結果。

當然,你不太可能以這裡所示的方式直接使用Future;代碼示例僅用於教育目的。你與asynccio的大部分聯繫都是通過Task實例進行的。

你可能想知道如果在Task實例上調用set_result()會發生什麼。在Python 3.8之前可以這樣做,但現在不允許這麼做了。任務實例是協程對象的包裝器,它們的結果值只能在內部設置為底層協程函數的結果,如 示例 3-17所示那樣。

示例 3-17. 在task上調用set_result

(L13)唯一的區別是我們創建的是Task實例而不是Future實例。當然,Task API要求我們提供一個協程;這裡我們使用sleep()只是因為簡單方便。

(L7)正在傳入一個Task實例。它滿足函數的類型簽名(因為Task是Future的子類),但從Python 3.8開始,我們不再允許在Task上調用set_result():嘗試這樣做將引發RuntimeError。這個想法是,一個Task代表一個正在運行的協程,所以結果應該總是來自於task自身。

(L10, L24)但是,我們仍然可以cancel()一個任務,它將在底層協程中引發CancelledError。

Create_task? Ensure_Future? 下定決心吧!

在第22頁的「快速入門」中,我說過運行協程的方法是使用asyncio.create_task()。在引入該函數之前,有必要獲取一個循環實例並使用loop.create_task()完成相同的任務。事實上,這也可以通過一個不同的模塊級函數來實現:asyncio.ensure_future()。一些開發人員推薦create_task(),而其他人推薦ensure_future()。

在我為這本書做研究的過程中,我確信API方法asyncio.ensure_future()是引起對asyncio庫廣泛誤解的罪魁禍首。API的大部分內容都非常清晰,但在學習過程中還存在一些嚴重的障礙,這就是其中之一。當你遇到ensure_future()時,你的大腦會非常努力地將其集成到關於asyncio應該如何使用的心理模型中——但很可能會失敗!

在Python 3.6 asyncio 文檔中,這個現在已經臭名昭著的解釋突出了 ensure_future() 的問題:

asyncio.ensure_future(coro_or_future, *, _loop =None)

安排執行一個協程對象:把它包裝在future中。返回一個Task對象。如果參數是Future,則直接返回。

什麼!? 當我第一次讀到這篇文章時,我很困惑。下面希望是對ensure_future()的更清楚的描述:

這個函數很好地說明了針對終端用戶開發人員的asyncio API(高級API)和針對框架設計人員的asyncio API(低級API)之間的區別。讓我們在示例 3-18中自習看看它是如何工作的。

示例 3-18. 仔細看看ensure_future()在做什麼

(L3)一個簡單的什麼都不做的協程函數。我們只需要一些能組成協程的東西。

(L6)我們通過直接調用該函數來創建協程對象。你的代碼很少會這樣做,但我想在這裡明確地表示,我們正在向每個create_task()和ensure_future()傳遞一個協程對象。

(L7)獲取一個循環。

(L9)首先,我們使用loop.create_task()在循環中調度協程,並返回一個新的Task實例。

(L10)驗證類型。到目前為止,沒有什麼有趣的。

(L12)我們展示了asyncio.ensure_future()可以被用來執行與create_task()相同的動作:我們傳入了一個協程,並返回了一個Task實例(並且協程已經被安排在循環中運行)!如果傳入的是協程,那麼loop.create_task()和asyncio.ensure_future()之間沒有區別。

(L15)如果我們給ensure_future()傳遞一個Task實例會發生什麼呢?注意我們要傳遞的Task實例是已經在第4步通過loop.create_task()創建好的。

(L16)返回的Task實例與傳入的Task實例完全相同:它在被傳遞時沒有被改變。

直接傳遞Future實例的意義何在?為什麼用同一個函數做兩件不同的事情?答案是,ensure_future()的目的是讓框架作者向最終用戶開發者提供可以處理兩種參數的API。不相信我?這是ex-BDFL自己說的:

ensure_future()的要點是,如果你有一個可能是協程或Future(後者包括一個Task,因為它是Future的子類)的東西,並且你想能夠調用一個只在Future上定義的方法(可能唯一有用的例子是cancel())。當它已經是Future(或Task)時,它什麼也不做;當它是協程時,它將它包裝在Task中。

如果您知道您有一個協程,並且希望它被調度,那麼正確的API是create_task()。唯一應該調用ensure_future()的時候是當你提供一個API(像大多數asyncio自己的API),它接受協程或Future,你需要對它做一些事情,需要你有一個Future。

—Guido van Rossum

總而言之,asyncio.sure_future()是一個為框架設計者準備的輔助函數。這一點最容易通過與一種更常見的函數進行類比來解釋,所以我們來做這個解釋。如果你有幾年的編程經驗,你可能已經見過類似於例3-19中的istify()函數的函數。示例 3-19中listify()的函數。

示例 3-19. 一個強制輸入列表的工具函數

這個函數試圖將參數轉換為一個列表,不管輸入的是什麼。api和框架中經常使用這類函數將輸入強制轉換為已知類型,這將簡化後續代碼——在本例中,您知道參數(來自listify()的輸出)將始終是一個列表。

如果我將listify()函數重命名為ensure_list(),那麼您應該開始看到與asyncio.ensure_future()的類似之處:它總是試圖將參數強制轉換為Future(或子類)類型。這是一個實用函數,它使框架開發人員(而不是像你我這樣的終端用戶開發人員)的工作變得更容易。

實際上,asyncio標準庫模塊本身使用ensure_future()正是出於這個原因。當你下次查看API時,你會發現函數參數被描述為「可等待對象」,很可能內部使用ensure_future()強制轉換參數。例如,asyncio.gather()函數就像下面的代碼一樣:

aws參數表示「可等待對象」,包括協程、task和future。在內部,gather()使用ensure_future()進行類型強制轉換:task和future保持不變,而把協程強制轉為task。

這裡的關鍵是,作為終端用戶應用程序開發人員,應該永遠不需要使用asyncio.ensure_future()。它更像是框架設計師的工具。如果你需要在事件循環上調度協程,只需直接使用asyncio.create_task()來完成。

在接下來的幾節中,我們將回到語言級別的特性,從異步上下文管理器開始。

原創文章,作者:MEUQ,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/133102.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
MEUQ的頭像MEUQ
上一篇 2024-10-03 23:56
下一篇 2024-10-03 23:56

相關推薦

  • Python中引入上一級目錄中函數

    Python中經常需要調用其他文件夾中的模塊或函數,其中一個常見的操作是引入上一級目錄中的函數。在此,我們將從多個角度詳細解釋如何在Python中引入上一級目錄的函數。 一、加入環…

    編程 2025-04-29
  • Python列表中負數的個數

    Python列表是一個有序的集合,可以存儲多個不同類型的元素。而負數是指小於0的整數。在Python列表中,我們想要找到負數的個數,可以通過以下幾個方面進行實現。 一、使用循環遍歷…

    編程 2025-04-29
  • 如何查看Anaconda中Python路徑

    對Anaconda中Python路徑即conda環境的查看進行詳細的闡述。 一、使用命令行查看 1、在Windows系統中,可以使用命令提示符(cmd)或者Anaconda Pro…

    編程 2025-04-29
  • Python計算陽曆日期對應周幾

    本文介紹如何通過Python計算任意陽曆日期對應周幾。 一、獲取日期 獲取日期可以通過Python內置的模塊datetime實現,示例代碼如下: from datetime imp…

    編程 2025-04-29
  • Python周杰倫代碼用法介紹

    本文將從多個方面對Python周杰倫代碼進行詳細的闡述。 一、代碼介紹 from urllib.request import urlopen from bs4 import Bea…

    編程 2025-04-29
  • 蝴蝶優化算法Python版

    蝴蝶優化算法是一種基於仿生學的優化算法,模仿自然界中的蝴蝶進行搜索。它可以應用於多個領域的優化問題,包括數學優化、工程問題、機器學習等。本文將從多個方面對蝴蝶優化算法Python版…

    編程 2025-04-29
  • Python字典去重複工具

    使用Python語言編寫字典去重複工具,可幫助用戶快速去重複。 一、字典去重複工具的需求 在使用Python編寫程序時,我們經常需要處理數據文件,其中包含了大量的重複數據。為了方便…

    編程 2025-04-29
  • Python清華鏡像下載

    Python清華鏡像是一個高質量的Python開發資源鏡像站,提供了Python及其相關的開發工具、框架和文檔的下載服務。本文將從以下幾個方面對Python清華鏡像下載進行詳細的闡…

    編程 2025-04-29
  • python強行終止程序快捷鍵

    本文將從多個方面對python強行終止程序快捷鍵進行詳細闡述,並提供相應代碼示例。 一、Ctrl+C快捷鍵 Ctrl+C快捷鍵是在終端中經常用來強行終止運行的程序。當你在終端中運行…

    編程 2025-04-29
  • Python程序需要編譯才能執行

    Python 被廣泛應用於數據分析、人工智能、科學計算等領域,它的靈活性和簡單易學的性質使得越來越多的人喜歡使用 Python 進行編程。然而,在 Python 中程序執行的方式不…

    編程 2025-04-29

發表回復

登錄後才能評論