如何编写一个Python程序的异步/同步变体?
首先开发一个简单的实现是正常的。例如,我们可以从一个非并发程序开始,然后添加并发性。我希望能够顺利地来回切换 例如,单线程(伪代码): 异步(伪代码): 使用Python如何编写一个Python程序的异步/同步变体?,python,python-3.x,async-await,python-asyncio,Python,Python 3.x,Async Await,Python Asyncio,首先开发一个简单的实现是正常的。例如,我们可以从一个非并发程序开始,然后添加并发性。我希望能够顺利地来回切换 例如,单线程(伪代码): 异步(伪代码): 使用Pythonasync/await,通常使用asyncio.run()包装运行(与普通程序中使用的循环相比);然后在调用层次结构的底部,使用IO操作,如aiohttp.ClientSession().get(url)(与普通的requests.request()) 但是,在异步版本中,这两者之间的调用层次结构中的所有函数都必须写成async
async/await
,通常使用asyncio.run()
包装运行(与普通程序中使用的循环相比);然后在调用层次结构的底部,使用IO操作,如aiohttp.ClientSession().get(url)
(与普通的requests.request()
)
但是,在异步版本中,这两者之间的调用层次结构中的所有函数都必须写成async/await
。因此,我需要编写基本相同的调用层次结构的两个副本,主要区别在于它们是否具有async/await
关键字
这是大量的代码重复
如何制作一个可切换的非并发/异步程序?这确实是一个很大的话题,但不是一般的话题。我个人有一个私有的WebDAV项目,它实现了同步版本和异步版本 首先,我的WebDAV客户端接受一个名为
client
的参数,该参数可以是requests.Session
或aiohttp.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.Session
和aiohttp.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(…)
那样使用它
这就是我处理两个不同版本的方式。我认为这个想法是,您可以通过函数调用传递这些协程,并且只在最高级别对它们进行评估。因此,您只需要在最后一个级别编写不同的代码,但在所有其他级别都具有相同的逻辑。Sync/async代码具有根本不同的运行时期望。假设回答这个问题有点太宽泛了;一个具体的例子可能更能说明问题。deceze:下面@sraw给出的答案显示了一个例子,或者查看上面我的伪代码谢谢。这是有道理的。但请注意,您的两个
ls
函数几乎是重复的代码,因此应该避免使用。@JoshuaFox同意,实际上,如果我不使用async/wait
关键字,而只使用旧的yield
样式和if
条件,就可以避免使用。但我们都知道,新的风格更清晰、更漂亮。此外,我相信没有办法完全避免重复的代码,我只是试图通过在最外部的级别上评估协同路由来复制最少的代码。
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)
# 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)