python2异步编程_Python 异步编程入门
本文是寫(xiě)給 JavaScript 程序員的 Python 教程。
Python 的異步編程,其他人可能覺(jué)得很難,但是 JavaScript 程序員應(yīng)該特別容易理解,因?yàn)閮烧叩母拍詈驼Z(yǔ)法類(lèi)似。JavaScript 的異步模型更簡(jiǎn)單直觀,很適合作為學(xué)習(xí) Python 異步的基礎(chǔ)。
本文解釋 Python 的異步模塊 asyncio 的概念和基本用法,并且演示如何通過(guò) Python 腳本操作無(wú)頭瀏覽器 pyppeteer 。
一、Python 異步編程的由來(lái)
歷史上,Python 并不支持專門(mén)的異步編程語(yǔ)法,因?yàn)椴恍枰?/p>
有了多線程(threading)和多進(jìn)程(multiprocessing),就沒(méi)必要一定支持異步了。如果一個(gè)線程(或進(jìn)程)阻塞,新建其他線程(或進(jìn)程)就可以了,程序不會(huì)卡死。
但是,多線程有"線程競(jìng)爭(zhēng)"的問(wèn)題,處理起來(lái)很復(fù)雜,還涉及加鎖。對(duì)于簡(jiǎn)單的異步任務(wù)來(lái)說(shuō)(比如與網(wǎng)頁(yè)互動(dòng)),寫(xiě)起來(lái)很麻煩。
Python 3.4 引入了 asyncio 模塊,增加了異步編程,跟 JavaScript 的async/await 極為類(lèi)似,大大方便了異步任務(wù)的處理。它受到了開(kāi)發(fā)者的歡迎,成為從 Python 2 升級(jí)到 Python 3 的主要理由之一。
二、asyncio 的設(shè)計(jì)
asyncio 模塊最大特點(diǎn)就是,只存在一個(gè)線程,跟 JavaScript 一樣。
由于只有一個(gè)線程,就不可能多個(gè)任務(wù)同時(shí)運(yùn)行。asyncio 是"多任務(wù)合作"模式(cooperative multitasking),允許異步任務(wù)交出執(zhí)行權(quán)給其他任務(wù),等到其他任務(wù)完成,再收回執(zhí)行權(quán)繼續(xù)往下執(zhí)行,這跟 JavaScript 也是一樣的。
由于代碼的執(zhí)行權(quán)在多個(gè)任務(wù)之間交換,所以看上去好像多個(gè)任務(wù)同時(shí)運(yùn)行,其實(shí)底層只有一個(gè)線程,多個(gè)任務(wù)分享運(yùn)行時(shí)間。
表面上,這是一個(gè)不合理的設(shè)計(jì),明明有多線程多進(jìn)程的能力,為什么放著多余的 CPU 核心不用,而只用一個(gè)線程呢?但是就像前面說(shuō)的,單線程簡(jiǎn)化了很多問(wèn)題,使得代碼邏輯變得簡(jiǎn)單,寫(xiě)法符合直覺(jué)。
asyncio 模塊在單線程上啟動(dòng)一個(gè)事件循環(huán)(event loop),時(shí)刻監(jiān)聽(tīng)新進(jìn)入循環(huán)的事件,加以處理,并不斷重復(fù)這個(gè)過(guò)程,直到異步任務(wù)結(jié)束。事件循環(huán)的內(nèi)部機(jī)制,可以參考 JavaScript 的模型,兩者是一樣的。
三、asyncio API
下面介紹 asyncio 模塊最主要的幾個(gè)API。注意,必須使用 Python 3.7 或更高版本,早期的語(yǔ)法已經(jīng)變了。
第一步,import 加載 asyncio 模塊。
import asyncio
第二步,函數(shù)前面加上 async 關(guān)鍵字,就變成了 async 函數(shù)。這種函數(shù)最大特點(diǎn)是執(zhí)行可以暫停,交出執(zhí)行權(quán)。
async def main():
第三步,在 async 函數(shù)內(nèi)部的異步任務(wù)前面,加上await命令。
await asyncio.sleep(1)
上面代碼中,asyncio.sleep(1) 方法可以生成一個(gè)異步任務(wù),休眠1秒鐘然后結(jié)束。
執(zhí)行引擎遇到await命令,就會(huì)在異步任務(wù)開(kāi)始執(zhí)行之后,暫停當(dāng)前 async 函數(shù)的執(zhí)行,把執(zhí)行權(quán)交給其他任務(wù)。等到異步任務(wù)結(jié)束,再把執(zhí)行權(quán)交回 async 函數(shù),繼續(xù)往下執(zhí)行。
第四步,async.run() 方法加載 async 函數(shù),啟動(dòng)事件循環(huán)。
asyncio.run(main())
上面代碼中,asyncio.run() 在事件循環(huán)上監(jiān)聽(tīng) async 函數(shù)main的執(zhí)行。等到 main 執(zhí)行完了,事件循環(huán)才會(huì)終止。
四、async 函數(shù)的示例
下面是 async 函數(shù)的例子,新建一個(gè)腳本async.py,代碼如下。
#!/usr/bin/env python3
# async.py
importasyncio
async defcount():
print("One")
await asyncio.sleep(1)
print("Two")
async defmain():
await asyncio.gather(count(), count(), count())
asyncio.run(main())
上面腳本中,在 async 函數(shù)main的里面,asyncio.gather() 方法將多個(gè)異步任務(wù)(三個(gè) count())包裝成一個(gè)新的異步任務(wù),必須等到內(nèi)部的多個(gè)異步任務(wù)都執(zhí)行結(jié)束,這個(gè)新的異步任務(wù)才會(huì)結(jié)束。
腳本的運(yùn)行結(jié)果如下。
$ python3 async.py
One
One
One
Two
Two
Two
上面運(yùn)行結(jié)果的原因是,三個(gè) count() 依次執(zhí)行,打印完 One,就休眠1秒鐘,把執(zhí)行權(quán)交給下一個(gè) count(),所以先連續(xù)打印出三個(gè) One。等到1秒鐘休眠結(jié)束,執(zhí)行權(quán)重新交回第一個(gè) count(),開(kāi)始執(zhí)行 await 命令下一行的語(yǔ)句,所以會(huì)接著打印出三個(gè)Two。腳本總的運(yùn)行時(shí)間是1秒。
作為對(duì)比,下面是這個(gè)例子的同步版本 sync.py。
#!/usr/bin/env python3
# sync.py
importtime
defcount():
print("One")
time.sleep(1)
print("Two")
defmain():
for_inrange(3):
count()
main()
上面腳本的運(yùn)行結(jié)果如下。
$ python3 sync.py
One
Two
One
Two
One
Two
上面運(yùn)行結(jié)果的原因是,三個(gè) count() 都是同步執(zhí)行,必須等到前一個(gè)執(zhí)行完,才能執(zhí)行后一個(gè)。腳本總的運(yùn)行時(shí)間是3秒。
五、實(shí)例:pyppeteer 模塊
最后是一個(gè)異步編程的真實(shí)例子:操作無(wú)頭瀏覽器。異步編程對(duì)代碼的簡(jiǎn)化,在這個(gè)例子體現(xiàn)得淋漓盡致。
我們需要用到 pyppeteer 模塊,它是無(wú)頭瀏覽器 Puppeteer 的 Python 移植,API 跟 JavaScript 版本基本一致。下面是安裝命令。
$ python3 -m pip install pyppeteer
然后,寫(xiě)一個(gè)網(wǎng)頁(yè)截圖腳本screenshot.py。
#!/usr/bin/env python3
# screenshot.py
importasyncio
frompyppeteerimportlaunch
async defmain():
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())
上面代碼中,啟動(dòng)瀏覽器(launch)、打開(kāi)新 Tab(newPage())、訪問(wèn)網(wǎng)址(page.goto())、截圖(page.screenshot())、關(guān)閉瀏覽器(browser.close()),這一系列操作都是異步任務(wù),使用 await 命令寫(xiě)起來(lái)非常自然簡(jiǎn)單。
執(zhí)行這個(gè)腳本,當(dāng)前目錄下就會(huì)生成截圖文件 example.png。
$ python3 screenshot.py
如果腳本執(zhí)行時(shí)報(bào)錯(cuò) No usable sandbox!,可以參考這里。另外,第一次執(zhí)行這個(gè)腳本,會(huì)下載安裝 Puppeteer,可能需要等待較長(zhǎng)時(shí)間,但是此后的執(zhí)行就會(huì)很快。
Pyppeteer 的官網(wǎng)還有其他實(shí)例,比如向網(wǎng)頁(yè)注入 JavaScript 代碼,大家可以自己試玩。
六、參考鏈接
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的python2异步编程_Python 异步编程入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: gg修改器怎么用教学
- 下一篇: python获取一个月之前日期_Pyth