Я хочу разрешить запуск fetcher.read()
как синхронно, так и асинхронно (например, через цикл обработки событий).
Не думаю, что этовозможно использование одной и той же функции через API синхронизации и асинхронного 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, без дублирования кода, потому что версия синхронизации состоит из тривиальных трамплинов,чье определение может быть дополнительно сжато с помощью соответствующих декораторов.
Модуль асинхронного извлечения должен иметь красивое короткое имя, чтобы пользователи не чувствовали себя наказанными за использование асинхронных функций.Он должен быть простым в использовании, и на самом деле он предоставляет множество новых функций по сравнению с API-интерфейсом синхронизации, в частности распараллеливание с минимальными издержками и надежное аннулирование.
Маршрут, который не рекомендуетсяиспользует run_in_executor
или аналогичный инструмент на основе потоков для запуска requests
в пуле потоков под колпаком.Эта реализация не дает реальных преимуществ использования asyncio, но несет все расходы.В этом случае лучше продолжить предоставление синхронного API и предоставить пользователям возможность использовать concurrent.futures
или аналогичные инструменты для параллельного выполнения, где они хотя бы знают, что используют потоки.