Python 非同步程式設計入門

阮一峰發表於2019-11-21

本文是寫給 JavaScript 程式設計師的 Python 教程。

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 程式碼,大家可以自己試玩。

六、參考連結

(正文完)

如何通過實戰專案快速提升 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 學習資料包。

(完)

相關文章