You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不修改源码的情况下将异步Python代码整合到同步函数中?

问题:异步改造外部同步HTTP库(不修改源码)

我有一个依赖requests模块执行HTTP请求的外部库,需要在尽量不使用多线程(仅作为最后备选)的前提下异步使用该库,且无法修改其源码。由于该库与requests的交互仅通过单个函数完成,猴子补丁似乎是可行方案,但不确定能否用带async关键字的异步函数替换同步函数。

简化后的问题代码如下:

import asyncio
import aiohttp
import types
import requests


# 无法修改的Library类
class Library:
    def do(self):
        self._request('example.com')

        # 其他业务代码...

    def _request(self, url):
        return requests.get(url).text


# 计划猴子补丁替换的异步方法
async def new_request(self, url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()


async def main():
    library = Library()

    # 执行猴子补丁
    library._request = types.MethodType(new_request, library)

    # 尝试异步调用库方法,期望用aiohttp执行请求
    asyncio.gather(
        library.do(),
        library.do(),
        library.do()
    )

    print('Done.')

asyncio.run(main())

运行后出现两个问题:

  • asyncio.gather调用时抛出TypeError: An asyncio.Future, a coroutine or an awaitable is required
  • self._request('example.com')处触发RuntimeWarning: coroutine 'new_request' was never awaited

请问:不修改Library类源码能否让代码正常运行?若不能,还有哪些方案可实现该库的异步请求?


解答

不修改Library类源码的前提下,直接用异步函数替换同步的_request方法无法让代码正常运行,原因有两点:

  1. 原do方法是同步逻辑,调用_request时不会执行await,导致异步协程被创建但从未触发执行,引发警告;
  2. library.do()是同步函数调用,返回值为None而非asyncio可识别的可等待对象(Future/协程),不符合asyncio.gather的参数要求,因此抛出类型错误。

以下是两种可行的替代方案:

方案1:同步包装异步请求(猴子补丁+线程池并行)

将异步请求逻辑用同步函数包装,在包装函数内通过事件循环执行异步请求,保留原do方法的调用逻辑不变,同时复用aiohttp的异步能力。若要并行执行多个do调用,需借助线程池避免阻塞事件循环(这属于你提到的最后备选多线程方案,但已是不修改源码下的最优解)。

示例代码:

import asyncio
import aiohttp
import types
import requests

# 无法修改的Library类
class Library:
    def do(self):
        result = self._request('http://example.com')
        # 原库中的其他业务代码...
        return result

    def _request(self, url):
        return requests.get(url).text

# 核心异步请求逻辑
async def _async_request(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

# 同步包装函数,用于猴子补丁
def sync_wrap_async_request(self, url):
    try:
        # 获取当前活跃的事件循环
        loop = asyncio.get_running_loop()
    except RuntimeError:
        # 无活跃循环时创建新循环
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    # 阻塞等待异步请求结果
    return loop.run_until_complete(_async_request(url))

async def main():
    library = Library()
    # 替换原_request方法
    library._request = types.MethodType(sync_wrap_async_request, library)
    
    # 用线程池包装同步do方法,实现并行执行
    await asyncio.gather(
        asyncio.to_thread(library.do),
        asyncio.to_thread(library.do),
        asyncio.to_thread(library.do)
    )
    print('Done.')

asyncio.run(main())

方案2:异步化do方法(依赖原逻辑可复制)

如果原Library.do的逻辑简单且长期稳定,可以给实例动态添加异步版本的do方法,直接调用异步的_request,无需依赖线程池。但此方法的缺点是需要完全复制原do的业务逻辑,若原库更新则会出现逻辑不一致的问题。

示例代码:

import asyncio
import aiohttp
import types
import requests

# 无法修改的Library类
class Library:
    def do(self):
        self._request('http://example.com')
        # 原库中的其他业务代码...

    def _request(self, url):
        return requests.get(url).text

# 异步版_request
async def async_request(self, url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

# 异步版do,完全复制原do的业务逻辑
async def async_do(self):
    await self._request('http://example.com')
    # 复制原do中的其他业务代码...

async def main():
    library = Library()
    # 替换_request和do方法
    library._request = types.MethodType(async_request, library)
    library.do = types.MethodType(async_do, library)
    
    # 直接用asyncio.gather执行异步方法
    await asyncio.gather(
        library.do(),
        library.do(),
        library.do()
    )
    print('Done.')

asyncio.run(main())

内容的提问来源于stack exchange,提问作者g00dds

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 19:41:54