282 lines
4.7 KiB
Markdown
282 lines
4.7 KiB
Markdown
|
# 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()
|
|||
|
<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](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
|