Python 高手编程系列三百零六:老 Python 版本中的 asyncio

asyncio 模块出现在 Python 3.4 中。因此,它是唯一的 Python 版本,在 Python 3.5 之
前对异步编程有着重要的支持。不幸的是,看起来这两个后续版本会引入兼容性的问题。
不管喜欢与否,Python 中异步编程的核心早于支持这种模式的语法元素。晚来总比没
有好,但是这引发了一种情况,其中有两个语法可用于协程。
从 Python 3.5 开始,你可以使用 async 和 await:
async def main():
await asyncio.sleep(0)
但对于 Python 3.4,你需要使用 asyncio.coroutine 装饰器和 yield from 语句:
@asyncio.couroutine
def main():
yield from asyncio.sleep(0)
另一个有用的事实是 yield from 语句的引入在 Python 3.3,并且在 PyPI 上有一个
asyncio 的可用的移植。这意味着你也可以使用 Python 3.3 的进行协同多任务处理的实现。
异步编程实例
正如本章中已经多次提到的,异步编程是处理 I/O 繁忙操作的一个很好的工具。因此,
现在开始构建一些比简单的打印序列或异步等待更实用的例子。
为了一致性,我们将尝试处理在多线程和多进程的帮助下解决同样的问题。因此,我
们将尝试通过网络连接异步地从外部资源获取一些数据。如果我们可以使用前面部分中的
相同的 python-gmaps 软件包,那就太好了。不幸的是,我们不能。
python-gmaps 的创建者有点懒惰,并且走了捷径。为了简化开发,他选择了 requests
包作为他的 HTTP 客户端的库。不幸的是,requests 不支持异步 I/O 的 async 和 await。还有
一些其他项目旨在为 requests 项目提供一些并发性,但它们要么依赖 Gevent(grequests,参考 https://github.com/kennethreitz/grequests),要么依赖线程/进程池执行(requests-
futures,参考 https://github.com/ross/requests-futures)。这些都不能解决我们的问题。
在前面的例子的库很容易使用,但是知道这些库的限制后,我们需要构建一些库来填
补这些空白。Google Maps API 的使用非常简单,因此我们将构建一个简单易用的异步实用
程序,仅用于解释说明的目的。Python 3.5 版本的标准库仍然缺少一个库,无法让异步 HTTP
请求与调用 urllib.urlopen()一样简单。我们绝对不想从头开始构建整个协议支持,
所以我们将使用 PyPI 上的 aiohttp 包,它会提供少量的帮助。这是一个非常有前途的库,
增加了异步 HTTP 的客户端和服务端实现。以下是一个基于 aiohttp 的小模块,它创建
了一个 geocode()辅助函数,它对 Google Maps API 服务进行地理编码请求:
import aiohttp
session = aiohttp.ClientSession()
async def geocode(place):
params = {
‘sensor’: ‘false’,
‘address’: place
}
async with session.get(
‘https://maps.googleapis.com/maps/api/geocode/json’,
params=params
) as response:
result = await response.json()
return result[‘results’]
我们假设这个代码保存在一个名为 asyncgmaps 的模块中,稍后我们将使用它。现在我
们准备重写在讨论多线程和多进程时使用的示例。以前,我们将整个操作分为两个单独的步骤。
• 使用 fetch_place()函数并行执行对外部服务的所有请求。
• 使用 present_result()函数显示循环中的所有结果。
但是因为协同多任务有时是完全不同于使用多进程或线程,我们可以稍微修改一下方
法。我们不再关注“每一项使用一个线程”部分中提到的大多数问题。协程是非抢占式的,
因此我们可以在等待 HTTP 响应后立即显示结果。这将简化我们的代码,使它更清楚:
import asyncio

注意: 本地模块提前引入

from asyncgmaps import geocode, session
PLACES = (
‘Reykjavik’, ‘Vien’, ‘Zadar’, ‘Venice’,
‘Wrocław’, ‘Bolognia’, ‘Berlin’, ‘Słubice’,
‘New York’, ‘Dehli’,
)
async def fetch_place(place):
return (await geocode(place))[0]
async def present_result(result):
geocoded = await result
print(“{:>25s}, {:6.2f}, {:6.2f}”.format(
geocoded[‘formatted_address’],
geocoded[‘geometry’][‘location’][‘lat’],
geocoded[‘geometry’][‘location’][‘lng’],
))
async def main():
await asyncio.wait([
present_result(fetch_place(place))
for place in PLACES
])
if name == “main”:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

如果没有关闭 ClientSession,aiohttp 会抛出问题

因此我们需要手动清理

loop.run_until_complete(session.close())
loop.close()

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值