Les Crash-Devs d'un Passionné

Utiliser enumerate() sur une fonction asynchrone

/Catégorie/python

Temps de lecture : 2 minutes

La fonction

enumerate()
permet de prendre un itérateur et de retourner un tuple avec l'index en cours d'itération

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
vowel = ["a", "e", "i", "o", "u", "y"]
for i, letter in enumerate(vowel):
  print(i, letter)

# 0 a
# 1 e
# 2 i
# 3 o
# 4 u
# 5 y

Cela est très pratique, évitant ainsi de créer une variable avec un incrément dans la boucle.

Si on passe par une fonction asynchrone nous devons utiliser le mot clé

await
afin d'exécuter la coroutine

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import asyncio 

async def vowel():
    return ["a", "e", "i", "o", "u", "y"]

async def main(): # Need to wrap in main function to manage async with asyncio
    for letter in await vowel(): # call coroutine with await keyword
        print(letter)

asyncio.run(main()) # Call the loop

# a
# e
# i
# o
# u
# y

Et avec l'implémentation de

enumerate()

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import asyncio 

async def vowel():
    return ["a", "e", "i", "o", "u", "y"]

async def main(): # Need to wrap in main function to manage async with asyncio
    for i, letter in enumerate(await vowel()):
        print(i, letter)

asyncio.run(main()) # Call the loop

# 0 a
# 1 e
# 2 i
# 3 o
# 4 u
# 5 y

Donc jusqu'ici tout se passe bien et on arrive à un fonctionnement attendu.

Si nous décidons maitenant que

vowel()
retourne un générateur avec l'instruction
yield
on remarque que cela ne fonctionne plus

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import asyncio 

async def vowel():
    for letter in ["a", "e", "i", "o", "u", "y"]:
        yield letter

async def main(): # Need to wrap in main function to manage async with asyncio
    for i, letter in enumerate(await vowel()):
        print(i, letter)

asyncio.run(main()) # Call the loop

#    for i, letter in enumerate(await vowel()):
#                               ^^^^^^^^^^^^^
# TypeError: object async_generator can't be used in 'await' expression

En effet

vowel()
est un générateur de type asynchrone et pour itérer dessus nous avons besoin de l'instruction
async for
. La PEP525 décrit son fonctionnement depuis la version python 3.6

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# import asyncio 

# async def vowel():
#     return ["a", "e", "i", "o", "u", "y"]

# async def main(): # Need to wrap in main function to manage async with asyncio
#     for i, letter in enumerate(await vowel()):
#         print(i, letter)

# asyncio.run(main()) # Call the loop

import asyncio 

async def vowel():
    for letter in ["a", "e", "i", "o", "u", "y"]:
        yield letter

async def main(): # Need to wrap in main function to manage async with asyncio
    async for letter in vowel():
        print(letter)

asyncio.run(main()) # Call the loop

Maintenant essayons de mettre en place

enumerate()
sur cet exemple

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import asyncio 

async def vowel():
    for letter in ["a", "e", "i", "o", "u", "y"]:
        yield letter

async def main(): # Need to wrap in main function to manage async with asyncio
    async for i, letter in enumerate(vowel()):
        print(i, letter)

asyncio.run(main()) # Call the loop

# async for i, letter in enumerate(vowel()):
#                        ^^^^^^^^^^^^^^^^^^
# TypeError: 'async_generator' object is not iterable

La fonction

enumerate()
permet d'itérer sur un itérateur synchrone car elle surcharge les dunder
__iter__
et
__next__
, elle ne prend pas en charge les coroutines.

Nous allons donc implémenter un fonctionnement similaire, à savoir une fonction asynchrone qui

await
la coroutine et qui nous retourne un générateur avec cette coroutine et l'incrément de boucle.

clipboard
Copier le code
1
2
3
4
5
async def async_generator_enumerate(generator, start=0):
   index = start
   async for coroutine in generator:
    yield index, coroutine
    index += 1

Et en reprenant l'exemple avec cette fonction

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import asyncio 

async def vowel():
    for letter in ["a", "e", "i", "o", "u", "y"]:
        yield letter

async def main(): # Need to wrap in main function to manage async with asyncio
    async for i, letter in async_generator_enumerate(vowel()):
        print(i, letter)

asyncio.run(main()) # Call the loop

# 0 a
# 1 e
# 2 i
# 3 o
# 4 u
# 5 y

Et voilà une implémentation de

enumerate()
sur un générateur asynchrone, et en prime on a revu le fonctionnement basique d'un générateur et d'une fonction asynchrone