From 7b95b3504cacb68db4ba220fb1b114da5e5e246e Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Fri, 10 Feb 2023 16:12:57 +0100 Subject: [PATCH] Import: Paris.py #14. --- 2018-paris.py-14-asyncio.md | 281 ++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 2018-paris.py-14-asyncio.md diff --git a/2018-paris.py-14-asyncio.md b/2018-paris.py-14-asyncio.md new file mode 100644 index 0000000..b419f71 --- /dev/null +++ b/2018-paris.py-14-asyncio.md @@ -0,0 +1,281 @@ +# 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