如何编写一个Python程序的异步/异步变体?

时间:2019-06-13 07:08:58

标签: python python-3.x async-await python-asyncio

首先开发一个简单的实现是正常的。因此,例如,我们可能从一个非并行程序开始,然后添加并发性。我希望能够平稳地来回切换。

例如,单线程(伪代码):

   results=[]
   for url in urls:
      # This then calls other functions that call yet others
      # in a call hierarchy, down to a requests.request() call.
      get_result_and_store_in_database(url) 

异步(伪代码):

# The following calls other functions that call yet others
# in a call hierarchy, down to an asyncio ClientSession().get() call.
# It runs HTTP requests and store the results in a database.
# The multiple URLs are processed concurrently.
asyncio.run(get_results_in_parallel_and_store_in_db(urls))

使用Python async/await,通常用asyncio.run()包装运行(与普通程序中使用的循环相比);然后在调用层次结构的底部,您使用aiohttp.ClientSession().get(url)之类的IO操作(与普通requests.request()相比)。

但是,在异步版本中,这两者之间的调用层次结构中的所有函数都必须写为async/await。因此,我需要编写两个基本相同的呼叫层次结构的副本,主要区别在于它们是否具有async/await关键字。

很多代码重复。

如何制作可切换的非并发/异步程序?

1 个答案:

答案 0 :(得分:1)

这确实是一个大话题,但不是一般话题。我个人有一个私有的WebDAV项目,该项目同时实现了同步版本和异步版本。

首先,我的WebDAV客户端接受一个名为client的参数,它可以是requests.Sessionaiohttp.ClientSession来执行同步请求或异步请求。

第二,我有一个基类来实现所有常见的逻辑,例如:

def _perform_dav_request(self, method, auth_tuple=None, client=None, **kwargs):
    auth_tuple = self._get_auth_tuple(auth_tuple)
    client = self._get_client(client)
    data = kwargs.get("data")
    headers = None
    url = None

    path = kwargs.get("path")
    if path:
        root_url = urljoin(self._base_url, self._dav_url)
        url = root_url + path

    from_path = kwargs.get("from_path")
    to_path = kwargs.get("to_path")
    if from_path and to_path:
        root_url = urljoin(self._base_url, self._dav_url)
        url = root_url + from_path
        destination = root_url + quote(to_path)

        headers = {
            "Destination": destination
        }

    return client.request(method, url, data=data, headers=headers, auth=auth_tuple)

事实是requests.Sessionaiohttp.ClientSession都支持几乎相同的API,因此在这里我可以使用模糊调用client.request(...)

第三,我必须导出不同的API:

# In async client
async def ls(self, path, auth_tuple=None, client=None):
    response = await self._perform_dav_request("PROPFIND", auth_tuple, client, path=path)

    if response.status == 207:
        return parse_ls(await response.read())
    raise WebDavHTTPError(response.status, await response.read())

# In sync client
def ls(self, path, auth_tuple=None, client=None):
    response = self._perform_dav_request("PROPFIND", auth_tuple, client, path=path)

    if response.status_code == 207:
        return parse_ls(response.content)
    raise WebDavHTTPError(response.status_code, response.content)

所以最终我的用户可以像dav = DAV(...)dav = AsyncDAV(...)那样使用它。

这就是我处理两个不同版本的方式。我认为,您可以通过函数调用传递这些协程,并且仅在最高级别进行评估。因此,您只需要在最后一级编写不同的代码,而在所有其他级别上具有相同的逻辑即可。