J'essaie de réécrire ce code Python2.7 dans le nouvel ordre mondial asynchrone:
def get_api_results(func, iterable):
pool = multiprocessing.Pool(5)
for res in pool.map(func, iterable):
yield res
map()
bloque jusqu'à ce que tous les résultats aient été calculés, j'essaie donc de réécrire ceci en tant qu'implémentation asynchrone qui produira des résultats dès qu'ils seront prêts. Comme map()
, les valeurs de retour doivent être retournées dans le même ordre que iterable
. J'ai essayé ceci (j'ai besoin de requests
en raison des exigences d'authentification héritées):
import requests
def get(i):
r = requests.get('https://example.com/api/items/%s' % i)
return i, r.json()
async def get_api_results():
loop = asyncio.get_event_loop()
futures = []
for n in range(1, 11):
futures.append(loop.run_in_executor(None, get, n))
async for f in futures:
k, v = await f
yield k, v
for r in get_api_results():
print(r)
mais avec Python 3.6 je reçois:
File "scratch.py", line 16, in <module>
for r in get_api_results():
TypeError: 'async_generator' object is not iterable
Comment puis-je accomplir cela?
En ce qui concerne votre ancien code (2.7) - le multitraitement est considéré comme un puissant remplacement pour le module de threading beaucoup plus simple pour le traitement simultané de tâches gourmandes en processeur, où le threading ne fonctionne pas si bien. Votre code n'est probablement pas lié au processeur - car il suffit de faire des requêtes HTTP - et le threading aurait peut-être suffi pour résoudre votre problème.
Cependant, au lieu d'utiliser threading
directement, Python 3+ a un module Nice appelé concurrent.futures celui avec une API plus propre via cool Executor
classes. Ce module est également disponible pour python 2.7 en tant que package externe .
Le code suivant fonctionne sur python 2 et python 3:
# For python 2, first run:
#
# pip install futures
#
from __future__ import print_function
import requests
from concurrent import futures
URLS = [
'http://httpbin.org/delay/1',
'http://httpbin.org/delay/3',
'http://httpbin.org/delay/6',
'http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.coooom/',
]
def fetch(url):
r = requests.get(url)
r.raise_for_status()
return r.content
def fetch_all(urls):
with futures.ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {executor.submit(fetch, url): url for url in urls}
print("All URLs submitted.")
for future in futures.as_completed(future_to_url):
url = future_to_url[future]
if future.exception() is None:
yield url, future.result()
else:
# print('%r generated an exception: %s' % (
# url, future.exception()))
yield url, None
for url, s in fetch_all(URLS):
status = "{:,.0f} bytes".format(len(s)) if s is not None else "Failed"
print('{}: {}'.format(url, status))
Ce code utilise futures.ThreadPoolExecutor
, Basé sur le filetage. Une grande partie de la magie est dans as_completed()
utilisée ici.
Votre code python 3.6 ci-dessus, utilise run_in_executor()
qui crée une futures.ProcessPoolExecutor()
, et n'utilise pas vraiment d'E/S asynchrone !!
Si vous voulez vraiment continuer avec asyncio, vous devrez utiliser un client HTTP qui prend en charge asyncio, tel que aiohttp . Voici un exemple de code:
import asyncio
import aiohttp
async def fetch(session, url):
print("Getting {}...".format(url))
async with session.get(url) as resp:
text = await resp.text()
return "{}: Got {} bytes".format(url, len(text))
async def fetch_all():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, "http://httpbin.org/delay/{}".format(delay))
for delay in (1, 1, 2, 3, 3)]
for task in asyncio.as_completed(tasks):
print(await task)
return "Done."
loop = asyncio.get_event_loop()
resp = loop.run_until_complete(fetch_all())
print(resp)
loop.close()
Comme vous pouvez le voir, asyncio
possède également une as_completed()
, qui utilise désormais une véritable E/S asynchrone, en utilisant un seul thread sur un processus.
Vous mettez votre boucle d'événement dans une autre co-routine. Ne fais pas ça. La boucle d'événements est le "pilote" le plus externe du code asynchrone et doit être exécutée de manière synchrone.
Si vous devez traiter les résultats récupérés, écrivez plus de coroutines qui le font. Ils pourraient extraire les données d'une file d'attente ou conduire directement la récupération.
Vous pouvez avoir une fonction principale qui récupère et traite les résultats, par exemple:
async def main(loop):
for n in range(1, 11):
future = loop.run_in_executor(None, get, n)
k, v = await future
# do something with the result
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
Je ferais aussi de la fonction get()
correctement asynchrone en utilisant une bibliothèque asynchrone comme aiohttp
afin que vous n'ayez pas du tout à utiliser l'exécuteur.