Python中篇:asyncio的事件循环+高阶API实战

简介: Python中篇:asyncio的事件循环+高阶API实战

1. 前言



讲解事件循环难免要涉及到API的操作,而API的调用与事件循环关联,所以将二者揉合在一起给大家讲明白高阶API如何与事件循环搭配使用。


2. 事件循环+同步调用API



import asyncio
import time
async def download(url):
    print("start download url")
    time.sleep(2)
    print("end download url")
if __name__ == "__main__":
    start = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(download("www.baidu.com"))
    print(time.time()-start)


结果:


start download url
end download url
2.0046780109405518

注意:首先asyncio已经帮我们实现了基于各个系统(linux/windows/mac)事件循环模块,比如我们这里通过asyncio.get_event_loop()获取事件循环对象,有了循环对象我们才能调度协称,获取协称的结果。


我们看到download下载函数先执行第一个print,再sleep 2s,然后再print,从结果来看就是这样的,没有问题。


但是当我们在并发的情况下,在看看会有什么问题:


#只需要修改loop.run_until_complete这个入参就行
#其他都没变化
loop.run_until_complete(asyncio.wait([download("www.baidu.com") for i in range(2)]))


结果:


start download url
end download url
start download url
end download url
4.008697748184204


看到了吧,结果令人非常不适,你期望的结果是2s左右返回,但是为啥4s左右才返回呢?结局真的让人很悲哀。


罪魁祸首是谁呢?就是因为异步代码中有同步调用,即download协称中有time.sleep同步调用。


事件循环是单线程运行的,所以当sleep之后阻塞单线程,没办法继续运行,所以只能变成同步调用,所以你会看到2个download任务同步返回,耗时4s。


那么如何解决这个问题呢?


3. 事件循环+异步调用API



用asyncio.sleep代替time.sleep

# 把download中time.sleep替换成await asyncio.sleep
await asyncio.sleep(2)


还是上面例子运行如下:

start download url
start download url
end download url
end download url
2.0069119930267334


我们可以看到两个任务并发运行,结果也是我们想要的2s,几乎可以认为两个任务是同时开始同时结束的,这就达到了异步编程的要求:要异步,所有的都要异步


4. 创建任务(Task)或者未来对象(Future)



上面我们讲解了事件循环如何调用协称来实现异步编程,其实事件循环不但可以调用协称,而且还可以调度Task或者Future对象,比如:


只需要修改loop.run_until_complete的入参即可 其他不用修改
loop.run_until_complete(asyncio.ensure_future(download("www.baidu.com")))


这里是通过asyncio.ensure_future创建Future对象,而run_until_complete的入参是多选的,协称/Future/Task都可以当作参数传递进去,比如创建Task对象:


# 解释同上
loop.run_until_complete(loop.create_task(download("www.baidu.com")))


通过loop.create_task创建Task对象,run_until_complete照样可以接受,为什么呢?因为Task是Future的派生类,所以二者都可以当作入参传递。还有asyncio.ensure_future的底层就是调用loop.create_task,所以看似ensure_future是和事件循环无关,但其实内部是有获取事件循环并且调用create_task的。


部分源码:
def ensure_future(coro_or_future, *, loop=None):
    if coroutines.iscoroutine(coro_or_future):
        if loop is None: //没有事件循环
            loop = events.get_event_loop() //获取loop, 这个和main中外面获取的loop是同一个,因为在单线程中并且是线程安全的。
        task = loop.create_task(coro_or_future)


以上两者运行结果都是一样的,如下:


start download url
end download url
2.0043089389801025


5. asyncio.wait和asyncio.gather的高阶API用法



asyncio.wait我们上面示例都在用,大致意思就是运行所有协称/Task/Future,直到他们完成,而它本身就是一个协称,所以可以被事件循环run_until_complete调度。

我们重点说下asyncio.gather的用法,这个函数是high-level的,就是高度抽象的,它比wait更加灵活和方便。比如:


# 和wait用法一样
loop.run_until_complete(asyncio.gather(download("www.baidu.com")))


如果更加灵活呢?


# 示例1 和wait差不多,就是前面得加*
    tasks = [download("www.baidu.com") for i in range(2)]
    loop.run_until_complete(asyncio.gather(*tasks))
# 示例2:分组运行
    tasks1 = [download("www.baidu.com") for i in range(2)]
    tasks2 = [download("www.baidu.com") for i in range(2)]
    loop.run_until_complete(asyncio.gather(*tasks1, *tasks2))


如果想要接受返回值呢?


import asyncio
import time
# download上面定义过了
async def run():
    tasks1 = [download("www.baidu.com") for i in range(2)]
    tasks2 = [download("www.baidu.com") for i in range(2)]
    ret = await asyncio.gather(*tasks1, *tasks2)
    print(ret)
if __name__ == "__main__":
    start = time.time()
    asyncio.run(run())
    print(time.time()-start)


结果:


start download url
start download url
start download url
start download url
end download url
end download url
end download url
end download url
['hello world', 'hello world', 'hello world', 'hello world']
2.005855083465576


我们看到各个协称执行的结果也都获取到了,日常开发中建议多用gather


6. 协称/Task/Future执行完之后想要回调怎么办?



很简单,虽然这种情况我们不多见,但是万一在项目中需要回调,那么只需要几行代码就可以搞定回调。


import asyncio
import time
from functools import partial
async def download(url):
    print("start download url")
    await asyncio.sleep(2)
    print("end download url")
    return "hello world" //协称返回的结果
def callback(url, future): //协称在return之前的回调
    print("回调了:", url)
if __name__ == "__main__":
    start = time.time()
    loop = asyncio.get_event_loop()
    future = asyncio.gather(download("www.baidu.com"))
    future.add_done_callback(partial(callback, "www.baidu.com")) //利用partial函数包装callback,因为add_done_callback添加回调只接受一个参数,所以这里必须得用partial包装成一个函数,那相应的callback需要在增加一个参数url,而且这个url必须放在参数前面,这样的话我们就可以在回调的时候传递多个参数了。
    loop.run_until_complete(future)
    print("协称运行的结果:", future.result())
    print(time.time()-start)


运行结果:


start download url
end download url
回调了: www.baidu.com
协称运行的结果: ['hello world']
2.0060088634490967


7. 小结



asyncio的高阶API和事件循环相结合起来,可以在python的异步编程中发挥巨大作用,你只需要像编写同步代码那样编写异步代码,只是异步多了几个语法规则而已,但这些都不是阻止你学习异步编程的脚步,你现在要做的就是立即掌握它们并且在项目中投入实战。

相关文章
|
6天前
|
JSON 安全 API
如何高效编写API接口:以Python与Flask为例
构建RESTful API的简明教程:使用Python的Flask框架,从环境准备(安装Python,设置虚拟环境,安装Flask)到编写首个API(包括获取用户列表和单个用户信息的路由)。运行API服务器并测试在`http://127.0.0.1:5000/users`。进阶话题包括安全、数据库集成、API文档生成和性能优化。【6月更文挑战第27天】
32 7
|
2天前
|
运维 知识图谱 Python
专为运维工程师设计!阿里藏经阁出品的Python实战手册被我搞来了
Python 可能是极少数既简单又强大的编程语言中的一种。更重要的是,用它来编程是非常快乐的事。 今天给小伙伴们分享的是阿里“藏经阁”出品的专门给运维工程师设计的Python实战手册
|
7天前
|
数据采集 JSON 数据可视化
【Python实战】Python对中国500强排行榜数据进行可视化分析
【Python实战】Python对中国500强排行榜数据进行可视化分析
|
6天前
|
JSON 安全 API
实战指南:使用PHP构建高性能API接口服务端
构建RESTful API的简要指南:使用PHP和Laravel,先安装Laravel并配置数据库,接着在`api.php`中定义资源路由,创建`PostController`处理CRUD操作,定义`Post`模型与数据库交互。使用Postman测试API功能,如创建文章。别忘了关注安全性、错误处理和性能优化。
18 2
|
7天前
|
JSON 数据可视化 API
技术心得:如何用Python和API收集与分析网络数据?
技术心得:如何用Python和API收集与分析网络数据?
13 2
|
3天前
|
数据采集 Python
揭秘淘宝商品信息:Python爬虫技术入门与实战指南
Python爬虫用于获取淘宝商品详情,依赖`requests`和`beautifulsoup4`库。安装这两个库后,定义函数`get_taobao_product_details`,发送GET请求模拟浏览器,解析HTML获取标题和价格。注意选择器需随页面结构更新,遵守爬虫政策,控制请求频率,处理异常,且数据只能用于合法目的。
|
4天前
|
Java UED Python
Python多线程编程实战技巧与性能优化策略
Python多线程编程实战技巧与性能优化策略
|
5天前
|
JSON 安全 API
API开发实战:从设计到部署的全流程指南
在数字化转型中,API成为系统集成的关键。本文引导读者逐步实践API开发: 1. 设计阶段确定需求,选择RESTful风格,例如天气查询API(/api/weather/{city}),返回JSON数据。 2. 使用Python和Flask实现API,处理GET请求,返回城市天气信息。 3. 进行测试,如用curl请求`http://localhost:5000/api/weather/Beijing`。 4. 文档化API,借助Flask-RESTPlus自动生成文档。 5. 部署到Heroku,创建`Procfile`,通过`heroku`命令推送代码。 【6月更文挑战第28天】
22 0
|
7天前
|
数据采集 XML 存储
【Python实战】Python多线程批量采集图片
【Python实战】Python多线程批量采集图片
|
7天前
|
数据采集 XML 存储
【Python实战】Python采集二手车数据——超详细讲解
【Python实战】Python采集二手车数据——超详细讲解