# asyncio (ou pas) ## Julien Palard - Software Engineer - cPython core dev - Coordinator of docs.python.org/fr/ - Python trainer - hackinscience.org # Coroutine Une coroutine est une fonction dont l'exécution peut être suspendue. ## Callback Hell ``` function pong_handler(client) { client.on('data', function (data) { client.on('data_written', function () { client.close() }); client.write(data) client.flush() }); } ``` ## Avec des coroutines ```python async def pong_handler(): client.write(await client.read()) await client.flush() client.close() ``` ## Les coroutines Pour être exhaustif, il existe deux types de coroutines en Python : - generator-based coroutines - native coroutines ## Generator-based coroutines ```pytho import types @types.coroutine def get_then_print(url): ... ``` ## Native coroutines ```python async def get_then_print(url): ... ``` ## Coroutines Une `coroutine`, renvoie un objet `coroutine` : ``` >>> async def tum(): ... print("tum") ... >>> tum() ``` ## Coroutines Qu'on peut manipuler : ``` >>> async def tum(): ... print("tum") ... >>> a_coroutine_object = tum() >>> a_coroutine_object.send(None) tum Traceback (most recent call last): File "", line 1, in StopIteration ``` Notes: As you can see, calling `tum()` did not execute the `print("tum")`, but calling `.send(None)` did (see PEP 342). L'appel de .send est fait par la main loop (asyncio.run). ## Coroutines Une coroutine renvoie juste un objet coroutine, donc à un moment, du code devra appeler la méthode `send` dessus. C'est le rôle d'une *main loop*. ## Récupérer un résultat Le résultat d'une coroutine est stocké dans l'exception `StopIteration`. Notes: Dans l'attribut `value`. ## await Il existe un mot clé dédié à la récupération de résultat d'une coroutine : `await` ## await Lorsqu'on lui donne un *awaitable*, `await` essaye d'obtenir son résultat. Si l'*awaitable* se suspend, `await` suspends à son tour la coroutine actuelle, récursivemet, jusqu'à l'appel à `send`. ## await ``` async def two(): return 2 async def four(): return await two() + await two() coro = four() coro.send(None) ``` Ce qui donne `StopIteration: 4`, de manière complètement synchrone, malgré le vocabulaire utilisé. ## Suspendre une coroutine. Ce n'est pas possible dans une coroutine. Mais toute la chaîne d'*await* peut-être suspendue en utilisant un `yield` depuis un *future-like object*. ## Future-like object Un `future-like object` est un object implémentant `__await__`, qui a le droit de `yield`. L'expression du yield traversera toute la stack d'`await` jusqu’au `send`. ## Awaitables Les [awaitables](https://www.python.org/dev/peps/pep-0492/#await-expression) sont des objets pouvant être « attendus » via un `await`. Donc c'est soit une `coroutine` soit un objet implémentant `__await__`. # Gérer ses coroutines Appelons simplement `send`, et attendons-nous à la fameuse `StopIteration` : ```python async def two(): return 2 async def four(): return await two() + await two() def coro_manager(coro): try: coro.send(None) except StopIteration as stop: return stop.value print(coro_manager(four())) ``` ## Gérer ses coroutines Mais si la coroutine se suspend, notre `coro_manager` ne suffira pas : ```python class Awaitable: def __await__(self): yield async def wont_terminate_here(): await Awaitable() print("Terminated") return 42 print(coro_manager(wont_terminate_here())) ``` ## Gérer ses coroutines Donc, un gestionnaire de coroutines doit rappeler send, comme celui-ci : ```python def frenetic_coro_manager(coro): try: while True: coro.send(None) except StopIteration as stop: return stop.value ``` ## Gérer ses coroutines Mais notre manager frénétique ne sait s'occuper que d'une seule routine, peut-on faire mieux ? ## Gérer ses coroutines On pourrait relancer le travail de l'une d'entre elles au hasard : ```python import random def frenetic_coros_manager(*coros): coros = list(coros) while coros: coro = random.choice(coros) try: coro.send(None) except StopIteration as stop: coros.remove(coro) ``` ## Gérer ses coroutines Essayons : ```python async def tum(): while True: await Awaitable() print("Tum") async def pak(): while True: await Awaitable() print("Pak") frenetic_coros_manager(tum(), pak()) ``` Ohh, deux `while True:` qui coopèrent ! # Questions? - mamot.fr/@mdk - github.com/JulienPalard - mdk on #python-fr on libera.chat