Import: Paris.py #14.
This commit is contained in:
parent
af2344a0d3
commit
7b95b3504c
|
@ -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()
|
||||||
|
<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
|
Loading…
Reference in New Issue