Python 的非同步程式設計,其他人可能覺得很難,但是 JavaScript 程式設計師應該特別容易理解,因為兩者的概念和語法類似。JavaScript 的非同步模型更簡單直觀,很適合作為學習 Python 非同步的基礎。
本文解釋 Python 的非同步模組 asyncio
的概念和基本用法,並且演示如何透過 Python 指令碼操作無頭瀏覽器 pyppeteer 。
一、Python 非同步程式設計的由來
歷史上,Python 並不支援專門的非同步程式設計語法,因為不需要。
有了多執行緒(threading
)和多程序(multiprocessing
),就沒必要一定支援非同步了。如果一個執行緒(或程序)阻塞,新建其他執行緒(或程序)就可以了,程式不會卡死。
但是,多執行緒有"執行緒競爭"的問題,處理起來很複雜,還涉及加鎖。對於簡單的非同步任務來說(比如與網頁互動),寫起來很麻煩。
Python 3.4 引入了 asyncio
模組,增加了非同步程式設計,跟 JavaScript 的async/await
極為類似,大大方便了非同步任務的處理。它受到了開發者的歡迎,成為從 Python 2 升級到 Python 3 的主要理由之一。
二、asyncio 的設計
asyncio
模組最大特點就是,只存在一個執行緒,跟 JavaScript 一樣。
由於只有一個執行緒,就不可能多個任務同時執行。asyncio 是"多工合作"模式(cooperative multitasking),允許非同步任務交出執行權給其他任務,等到其他任務完成,再收回執行權繼續往下執行,這跟 JavaScript 也是一樣的。
由於程式碼的執行權在多個任務之間交換,所以看上去好像多個任務同時執行,其實底層只有一個執行緒,多個任務分享執行時間。
表面上,這是一個不合理的設計,明明有多執行緒多程序的能力,為什麼放著多餘的 CPU 核心不用,而只用一個執行緒呢?但是就像前面說的,單執行緒簡化了很多問題,使得程式碼邏輯變得簡單,寫法符合直覺。
asyncio 模組在單執行緒上啟動一個事件迴圈(event loop),時刻監聽新進入迴圈的事件,加以處理,並不斷重複這個過程,直到非同步任務結束。事件迴圈的內部機制,可以參考 JavaScript 的模型,兩者是一樣的。
三、asyncio API
下面介紹 asyncio
模組最主要的幾個API。注意,必須使用 Python 3.7 或更高版本,早期的語法已經變了。
第一步,import
載入 asyncio
模組。
import asyncio
第二步,函式前面加上 async
關鍵字,就變成了 async 函式。這種函式最大特點是執行可以暫停,交出執行權。
async def main():
第三步,在 async 函式內部的非同步任務前面,加上await
命令。
await asyncio.sleep(1)
上面程式碼中,asyncio.sleep(1)
方法可以生成一個非同步任務,休眠1秒鐘然後結束。
執行引擎遇到await
命令,就會在非同步任務開始執行之後,暫停當前 async 函式的執行,把執行權交給其他任務。等到非同步任務結束,再把執行權交回 async 函式,繼續往下執行。
第四步,async.run()
方法載入 async 函式,啟動事件迴圈。
asyncio.run(main())
上面程式碼中,asyncio.run()
在事件迴圈上監聽 async 函式main
的執行。等到 main
執行完了,事件迴圈才會終止。
四、async 函式的示例
下面是 async 函式的例子,新建一個指令碼async.py
,程式碼如下。
#!/usr/bin/env python3 # async.py import asyncio async def count(): print("One") await asyncio.sleep(1) print("Two") async def main(): await asyncio.gather(count(), count(), count()) asyncio.run(main())
上面指令碼中,在 async 函式main
的裡面,asyncio.gather()
方法將多個非同步任務(三個 count()
)包裝成一個新的非同步任務,必須等到內部的多個非同步任務都執行結束,這個新的非同步任務才會結束。
指令碼的執行結果如下。
$ python3 async.py One One One Two Two Two
上面執行結果的原因是,三個 count()
依次執行,列印完 One
,就休眠1秒鐘,把執行權交給下一個 count()
,所以先連續列印出三個 One
。等到1秒鐘休眠結束,執行權重新交回第一個 count()
,開始執行 await
命令下一行的語句,所以會接著列印出三個Two
。指令碼總的執行時間是1秒。
作為對比,下面是這個例子的同步版本 sync.py
。
#!/usr/bin/env python3 # sync.py import time def count(): print("One") time.sleep(1) print("Two") def main(): for _ in range(3): count() main()
上面指令碼的執行結果如下。
$ python3 sync.py One Two One Two One Two
上面執行結果的原因是,三個 count()
都是同步執行,必須等到前一個執行完,才能執行後一個。指令碼總的執行時間是3秒。
五、例項:pyppeteer 模組
最後是一個非同步程式設計的真例項子:操作無頭瀏覽器。非同步程式設計對程式碼的簡化,在這個例子體現得淋漓盡致。
我們需要用到 pyppeteer 模組,它是無頭瀏覽器 Puppeteer 的 Python 移植,API 跟 JavaScript 版本基本一致。下面是安裝命令。
$ python3 -m pip install pyppeteer
然後,寫一個網頁截圖指令碼screenshot.py
。
#!/usr/bin/env python3 # screenshot.py import asyncio from pyppeteer import launch async def main(): browser = await launch() page = await browser.newPage() await page.goto('http://example.com') await page.screenshot({'path': 'example.png'}) await browser.close() asyncio.run(main())
上面程式碼中,啟動瀏覽器(launch
)、開啟新 Tab(newPage()
)、訪問網址(page.goto()
)、截圖(page.screenshot()
)、關閉瀏覽器(browser.close()
),這一系列操作都是非同步任務,使用 await
命令寫起來非常自然簡單。
執行這個指令碼,當前目錄下就會生成截圖檔案 example.png
。
$ python3 screenshot.py
如果指令碼執行時報錯 No usable sandbox!
,可以參考這裡。另外,第一次執行這個指令碼,會下載安裝 Puppeteer,可能需要等待較長時間,但是此後的執行就會很快。
Pyppeteer 的官網還有其他例項,比如向網頁注入 JavaScript 程式碼,大家可以自己試玩。
六、參考連結
- Async IO in Python: A Complete Walkthrough, Brad Solomon
(正文完)
如何透過實戰專案快速提升 Python 開發技能?
Python 是當下最火的程式語言,房地產大佬潘石屹都說要學。
它上手極為簡單,短時間內你就能寫出解決實際問題的小程式,甚至去面試初級 Python 工程師的職位。
不過,要寫出更復雜的應用,或者從事資料分析、機器學習、Web 開發等工作,就需要正規系統的學習了。建議從一個簡單的小專案開始,然後不斷完善功能,去學習更多新東西。
- 第一步:寫一個最簡單的爬蟲,比如獲取 B 站的彈幕或豆瓣的書評影評。
- 第二步:單執行緒爬蟲擴充套件為多執行緒爬蟲,瞭解程序、執行緒、鎖。
- 第三步:對收集的資料進行清洗和分析。
- 第四步:將資料包告在 Web 端展示,瞭解 MVC 設計模式、Web 框架、資料庫操作。
完成以上四步,就從一個初級 Python 使用者成長為一名熟練工了。當然說起來簡單,真正實踐起來並不容易。每一步都會有比較多的坑,對於沒有經驗的人來說,自學效率比較低。如果有一個經驗豐富的老師帶,效果會好很多。
尹會生,金山公司西山居運維總監,在極客時間講過《零基礎學Python》和《Linux實戰技能100講》兩個課程,參與編寫過 《白話大資料與機器學習》 《運維前線》等書籍。
他與極客時間合作,推出了線下+線上相結合的《Python 進階訓練營》,手把手、面對面地幫助你,50天內實現 Python 開發技能的進階和突破,完成上面四步,從初級使用者成長為專業選手。
- 4 個實戰專案串聯起全部關鍵知識
- 4 天線下教學 + 5 次線上直播 + 7 周刻意練習 + 助教每日答疑
- 高效學習社群 + 班主任帶班
- 一線大廠和 TGO 鯤鵬會600多家企業面試直通車。優秀畢業生一年內獲得兩次企業內推服務。
原價 ¥3600, 早鳥特惠 ¥2499,早鳥僅限 100 人 ,微信掃描下方二維碼,立即加入????
無論是否報名,微信掃描下方二維碼,即可免費獲取 Python 學習資料包。
(完)