open1024.fr

Libérez vos octets !

Outils pour utilisateurs

Outils du site


Panneau latéral

developpement:python:notes_mooc_python3_asyncio

Semaine 8. Programmation asynchrone - asyncio

2. Quelques exemples simples

Un traitement

import asyncio
# coroutine
async def morceaux(message):
    # on appelle le code synchrone normalement
    print(message, "début")
    # avec await on rend la main
    await asyncio.sleep(0.5)
 
    print(message, "milieu")
    await asyncio.sleep(1)
 
    print(message, "fin")
    return f'{message} par morceaux'

Boucle d'événements

loop = asyncio.get_event_loop()
loop.run_until_complete(morceaux()"run")
 
# exécution du code:
run début
run milieu
run fin
'run par morceau'

Plusieurs traitements

loop.run_until_complete( 
        asyncio.gather( morceaux("run1"),
                        morceaux("run2") ) )
 
# exécution du code :
run1 début
run2 début
run1 milieu
run2 milieu
run1 fin
run2 fin
['run1 par morceaux', 'run2 par morceaux']

Ce qu'il ne faut pas faire

import time
 
async def famine(message):
    print(message, "début")
    # avec await on rend la main
    await asycio.sleep(0.5)
 
    print(message, "milieu")
    # /!\ on garde la main au lieu de la rendre
    time.sleep(1)
    print(message, "fin")
    return f'{message} par famine'
 
loop.run_until_complete( 
        asyncio.gather( morceaux("run1"),
                        morceaux("run2") ) )
 
# exécution du code :
run1 début
run2 début
run1 milieu    # /!\ fonctionnement différent
run1 fin
run2 milieu
run2 fin
['run1 par famine', 'run2 par famine']

Conclusion

  • on crée une fonction coroutine avec async def
  • une boucle d'événements
    • pour orchestrer plusieurs coroutines
  • une coroutine peut appeler une autre coroutine avec await
  • une coroutine peut appeler une fonction synchrone
    • mais attention à ne pas bloquer trop longtemps

3. asyncio: historique et écosystème

  • stable depuis 3.6
  • performances OK
  • nombreux protocoles réseau disponibles
    • HTTP aiohttp
    • ssh asyncssh
    • telnet telnetlib3
  • drivers bases de données
    • postgres asyncpg

Mais …

  • paradigme contagieux
  • travaux expérimentaux (post-asyncio)
    • uvloop
    • curio
    • trio

4. Extensions asynchrones du langage

Accès http:

import time
 
urls = ["http://www.iris.gov/pub/irs-pdf/f1040.pdf",
            "http://www.iris.gov/pub/irs-pdf/f1040ez.pdf",
            "http://www.iris.gov/pub/irs-pdf/f1040es.pdf",
            "http://www.iris.gov/pub/irs-pdf/f1040sb.pdf"]

En version séquentielle

import requests
 
beg = time.time()
 
for url in urls:
    req = requests.get(url)
    print(f"{url} returned {len(req.text)} chars")
 
print(f"duration = {time.time() - beg}s")

Résultats:
http://www.iris.gov/pub/irs-pdf/f1040.pdf 185539 chars
http://www.iris.gov/pub/irs-pdf/f1040ez.pdf 109087 chars
http://www.iris.gov/pub/irs-pdf/f1040es.pdf 395201 chars
http://www.iris.gov/pub/irs-pdf/f1040sb.pdf 105795 chars
duration = 11.0059…

En version asynchrone

import asyncio
import aiohttp
 
async def fetch(url):
    async with aiohttp.ClientSession() as session:
        print(f"fetching {url}")
 
        async with session.get(url) as response:
            #print(f"{url} returned status {response.status}")
            raw = await response.read()
            print(f"{url} returned {len(raw)} bytes")

Context managers asynchrone

  • aenter et aexit : awaitables
  • définit dans PEP492
# une coroutine qui va chercher toutes les URLs
# ne fait toujours rien, naturellement
asyncio def fetch_urls():
    await asyncio.gather(*(fetch(url) for url in urls))
 
loop = asyncio.get_event_loop()
beg = time.time()
loop.run_until_complete(fetch_urls())
print(f"duration = {time.time() - beg}s"

Résultat : fetching fttp:www.irs.gov/pub/irs-pdf/f1040sb.pdf fetching fttp:www.irs.gov/pub/irs-pdf/f1040.pdf fetching fttp:www.irs.gov/pub/irs-pdf/f1040ez.pdf fetching fttp:www.irs.gov/pub/irs-pdf/f1040es.pdf fttp:www.irs.gov/pub/irs-pdf/f1040sb.pdf returned 109984 bytes fttp:www.irs.gov/pub/irs-pdf/f1040ez.pdf returned 113498 bytes fttp:www.irs.gov/pub/irs-pdf/f1040.pdf returned 192545 bytes fttp:www.irs.gov/pub/irs-pdf/f1040es.pdf returned 409193 bytes duration = 7.2482380867004395s

Itérations asynchrone

  • boucle async for PEP-492
  • compréhensions asynchrones PEP-530
import asyncio
 
# une variante
async def fetch2(url, i):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            #avec ici une itération asynchrone
            async for line in response.content:
                print(f'{i}', end='')
    return url
 
asyncio.get_event_loop().run_until_complete(
    asyncio.gather(*(fetch2(url, i) for i, url in enumerate(urls)))

Résultat:
00000000000000000000000..

Résumé (1)

  • fonction coroutine async def foo()
  • foo() → objet coroutine: faire await foo()

Autorisé

async def foo():
    await bar()

Pas autorisé: SyntaxError

def foo():
    await bar()

Résumé (2)

  • async with
  • async for
  • Boucle d'événements
    • asyncio.get_event_loop()

5. Coroutines et awaitables

my first loop

Protocole awaitable

instruction classe d'objets protocole exemple
for itérables iter liste, ensemble
with context managers enter & exit fichier
dict[x] hachables hash builtins immuables
await awaitables await objet coroutine

await renvoie un itérateur

class Awaitables():
    def __await__(self):
        print("in awaitable")
        yield "yielded"
 
# il nous faut au moins une coroutine 
# pour faire await
async def main()
    await Awaitable()
 
# l'objet coroutine
coro = main()
 
coro.send(None)

Résultat:
in awaitable
out: yielded

Un peu moins simple

# itérateur à deux coups
class Awaitable2():
    def __await__(self)
        print("step1")
        yeld "yeld 1"
        print("step2")
        yeld "yeld 2"
        return "returned"
# boilerplate
async def main()
    return await Awaitable2()
# l'objet coroutine
coro = main()
coro.send(None)

Résultat
step1
out: 'yield 1'

coro.send(None)

Résultat
step2
out: 'yield 2'

try:
    coro.send(None)
exept Exception as e:
    x = e
    print('OOPS', type(e), e.value)

Résultat
OOPS <class 'SoptIteration'> returned

Plusieurs travaux en même temps

pile, await et yield

class w1:
    def __init__(self, marker):
        self.marker = marker
    def __await__(self):
        # redonner la main à la boucle
        yieldf"yeld {self.marker}"
        # retourné à await
        return 1
async def w2():
    return await w1('first') + await w1('second')
async def w3():
    return await w2() + 1
async def w4():
    return await w3() + 1
 
coro = w4()
coro.send(None)
out: 'yield first'
coro.send(None)
out: 'yield second'
try:
    coro.send(None)
execept Exception as e:
    x = e
    print('OOPS', type(e), e.value)
 
out: OOPS <class 'StopIteration' > 4
Voir animation à 6 minutes

Dans les deux sens

REPRENDRE LA VIDEO A 7:20

6. Boucles d'événement

7. Tâches et exeptions

8. La librairie asyncio

developpement/python/notes_mooc_python3_asyncio.txt · Dernière modification: 2021/07/02 10:40 de jc_online