4.7 KiB
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
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
import types
@types.coroutine
def get_then_print(url):
...
Native coroutines
async def get_then_print(url):
...
Coroutines
Une coroutine
, renvoie un objet coroutine
:
>>> async def tum():
... print("tum")
...
>>> tum()
<coroutine object tum at 0x7fa294538468>
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 "<stdin>", line 1, in <module>
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
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
:
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 :
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 :
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 :
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 :
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