talks/2018-paris.py-14-asyncio.md

282 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` jusquau `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