Как мне написать асинхронные / синхронные варианты одной программы на Python? - PullRequest
1 голос
/ 13 июня 2019

Нормально сначала разработать простую реализацию.Так, например, мы можем начать с непараллельной программы, а затем добавить параллелизм.Я хотел бы иметь возможность плавного переключения вперед и назад.

Например, однопоточный (псевдокод):

   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) (по сравнению с обычным requests.request().)

Но , в асинхронной версии все функции виерархия вызовов между этими двумя должна быть записана как async/await.Итак, мне нужно написать две копии одной и той же иерархии вызовов, различаясь в основном тем, имеют ли они ключевые слова async/await.

Это многократное дублирование кода.

Как я могу сделать переключаемую непараллельную / асинхронную программу?

1 Ответ

1 голос
/ 13 июня 2019

Это действительно большая тема, но не общая.У меня лично есть частный проект 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(...).

Так я работаю с двумя разными версиями.Я думаю, что идея заключается в том, что вы можете передавать эти сопрограммы через вызовы функций и оценивать их только на самом высоком уровне.Таким образом, вам нужно только написать другой код на последнем уровне, но иметь ту же логику на всех других уровнях.

...