Python中的协程使用举例
新冠肺炎放假快一个月,新年一开盘暴跌,就又想写跟踪大盘指数的代码了……
由于GIL名声在外,所以Python的线程编程我是从来没实际操作过……不过随着Python3中各大版本对协程支持的力度加大,在同一个进程内,可以考虑通过协程增加代码的执行效率了,尤其是在I/O部分。
在Python3.6/7/8中(尤其是7/8),对协程的支持使得编写一个协程变得异常简单,这里就介绍两种最常见的情况。(本文使用Python3.8版本)
1 异步执行传统函数
举个例子,比如我希望requests.get
多个页面http://www.baidu.com
,可以这样写:
1 | async def make_requests(): |
主要就是将普通函数放入loop.run_in_executor
中生成一个future
对象,然后把这些future
交给asyncio.gather
去统一执行,并返回结果。
- 注意,
requests
的复杂请求,如post
请求或通过requests.Session
设置header
后再通过session.send
发送的请求,使用asyncio.gather
执行会报requests.exceptions.InvalidSchema
异常,我猜测可能因为复杂请求调用了线程相关的适配器。
代码异步请求了5次百度页面,结果如下,用timeit
测试比同步请求快了4倍左右,还是符合期望的:
1 | [<Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>] |
2 异步执行协程
这个就更简单了,依然用上面的例子,requests
这个库通常是用在同步场景中的。如果希望既要使用异步还要享受requests
的友好API,可以试试httpx
,这个库包含了异步调用的请求支持,API与同步版的几乎没有差别,十分方便:
1 | async def make_requests(): |
可以看到httpx
直接提供了用于异步请求的AsyncCLient
,即使是复杂请求也可以进行异步请求了。我们的主要工作就是通过asyncio.create_task
批量创建Task
,再统一await task
即可。
- 注意,始终要记得
await
其实就是yield
的语法糖,如果创建一个task
直接await
,然后再创建下一个,就变成同步请求了。
结果以及获得结果的速度与第一节的例子相差无几:
1 | [<Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>, <Response [200 OK]>] |
3 总结
通过协程在同一个进程内进行诸如I/O请求的耗时操作,可以极大的提升代码执行效率,而且随着Python大版本的更新,将引入越来越多的异步支持。