【问题标题】:Enhance synchronous software API to allow asynchronous consuming增强同步软件 API 以允许异步消费
【发布时间】:2018-11-28 14:28:42
【问题描述】:

我在 Python 3.5+ 中有一个模块,它提供了一个从远程 Web API 读取一些数据并返回它的函数。该函数依赖于一个包装函数,该函数又使用库 requests 进行 HTTP 调用。

在这里(故意省略所有数据验证逻辑和异常处理):

# module fetcher.py

import requests

# high-level module API
def read(some_params):
    resp = requests.get('http://example.com', params=some_params)
    return resp.json()

# wrapper for the actual remote API call
def get_data(some_params):
    return call_web_api(some_params)

该模块当前被多个客户端导入和使用。

到目前为止,对 get_data 的调用本质上是同步的:这意味着使用函数fetcher.read() 的人都知道这将阻塞执行该函数的线程。

我希望实现的目标

我想允许fetcher.read() 以同步和异步方式同时运行(例如,通过事件循环)。 这是为了保持与使用模块的现有调用者的兼容性,同时提供可能性 利用非阻塞调用为想要异步调用函数的调用者提供更好的吞吐量。

这就是说,我的合法愿望是尽可能少地修改原始代码......

截至今天,我唯一知道的是 Requests 不支持开箱即用的异步操作,因此我应该切换到异步友好的 HTTP 客户端(例如aiohttp)以提供非阻止行为

需要如何修改上述代码以满足我的需求?这也让我问:有没有关于将同步软件 API 增强为异步上下文的最佳实践?强>

【问题讨论】:

    标签: python asynchronous refactoring python-asyncio


    【解决方案1】:

    我想让fetcher.read() 以同步和异步方式运行(例如,通过事件循环)。

    我认为通过同步和异步 API 都可以使用同一个函数是不可行的,因为使用模式是如此不同。即使你能以某种方式使它工作,也很容易把事情搞砸,特别是考虑到 Python 的动态类型特性。 (例如,用户可能会不小心忘记在异步代码中await 他们的函数,同步代码会启动,从而阻塞他们的事件循环。)

    相反,我建议实际的 API 是异步的,并创建一个简单的同步包装器,它只使用 run_until_complete 调用入口点。大致如下:

    # new module afetcher.py (or fetcher_async, or however you like it)
    
    import aiohttp
    
    # high-level module API
    async def read(some_params):
        async with aiohttp.request('GET', 'http://example.com', params=some_params) as resp:
            return await resp.json()
    
    # wrapper for the actual remote API call
    async def get_data(some_params):
        return call_web_api(some_params)
    

    是的,您从使用 requests 切换到 aiohttp,但这种变化是机械性的,因为 API 在本质上非常相似。

    同步模块的存在是为了向后兼容和方便,并且会简单地包装异步功能:

    # module fetcher.py
    
    import afetcher
    
    def read(some_params):
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(afetcher.read(some_params))
    
    ...
    

    此方法提供 API 的同步和异步版本,无需重复代码,因为同步版本由琐碎的蹦床组成,其定义可以使用适当的装饰器进一步压缩。

    async fetcher 模块应该有一个漂亮的短名称,这样用户就不会因为使用 async 功能而受到惩罚。它应该易于使用,并且与同步 API 相比,它实际上提供了许多新功能,最显着的是低开销并行化和可靠的取消。

    推荐的路线是使用run_in_executor 或类似的基于线程的工具在后台线程池中运行requests。该实现不提供使用 asyncio 的实际好处,但会产生所有成本。在这种情况下,最好继续提供同步 API,让用户使用concurrent.futures 或类似工具进行并行执行,至少他们知道自己正在使用线程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-19
      • 1970-01-01
      • 2017-01-11
      • 2019-07-02
      • 1970-01-01
      • 2014-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多