web-dev-qa-db-fra.com

Le générateur asynchrone n'est pas un itérateur?

Dans Python vous pouvez écrire un générateur qui est itérable comme:

def generate(count):
    for x in range(count):
        yield x

# as an iterator you can apply the function next() to get the values.
it = generate(10)
r0 = next(it)
r1 = next(it) ...

Lorsque vous essayez d'utiliser un itérateur asynchrone, vous obtenez l'erreur 'yield inside async'. La solution suggérée est d'implémenter votre propre générateur:

class async_generator:
    def __aiter__(self):
        return self
    async def __anext__(self):
        await asyncio.sleep()
        return random.randint(0, 10)

# But when you try to get the next element
it = async_generator(10)
r0 = next(it)

Vous obtenez l'erreur "L'objet 'async_generator' n'est pas un itérateur"

Je pense que si vous allez appeler quelque chose un Iterator, c'est parce qu'il a exactement la même interface, donc je peux simplement écrire des itérateurs asynchrones et utiliser un framework qui s'appuie fortement sur les appels next (). Toute nouvelle capacité Python est inutile si vous devez réécrire tout votre code pour pouvoir utiliser async.

Suis-je en train de manquer quelque chose?

Merci!

19
user1275011

Ainsi, comme l'a dit @bosnjak, vous pouvez utiliser async pour:

async for ITEM in A_ITER:
    BLOCK1
else: # optional
    BLOCK2

Mais si vous souhaitez itérer manuellement, vous pouvez simplement écrire:

it = async_iterator()
await it.__anext__()

Mais je ne recommanderais pas de faire ça.

Je pense que si vous allez appeler quelque chose un Iterator, c'est parce qu'il a exactement la même interface, donc je peux simplement écrire des itérateurs asynchrones et utiliser un framework qui s'appuie fortement sur les appels next ()

Non, en fait, ce n'est pas pareil. Il existe une différence entre les itérateurs synchrones réguliers et les itérateurs asynchrones. Et il y a peu de raisons à cela:

  1. Les coroutines Python sont construites au-dessus des générateurs en interne
  2. Selon Zen of python, explicite vaut mieux qu'implicite. Pour que vous puissiez voir où le code peut être suspendu.

C'est pourquoi il est impossible d'utiliser iter et next avec des itérateurs asynchrones. Et vous ne pouvez pas les utiliser avec des frameworks qui attendent des itérateurs synchrones. Donc, si vous voulez rendre votre code asynchrone, vous devez également utiliser des frameworks asynchrones. Ici sont quelques-uns d'entre eux.

Je voudrais également dire quelques mots sur les itérateurs et les générateurs. Iterator est un objet spécial qui a __iter__ et __next__ méthodes. Alors que générateur est une fonction spéciale contenant l'expression yield. Chaque générateur est un itérateur, mais pas l'inverse . La même chose est acceptable pour les itérateurs et générateurs asynchrones. Oui, puisque python 3.6 vous pouvez écrire des générateurs asynchrones!

async def ticker(delay, to):
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

Vous pouvez lire PEP 525 pour plus de détails

10

Je crois qu'une nouvelle déclaration a été introduite pour les générateurs asynchrones:

async for TARGET in ITER:
    BLOCK
else:
    BLOCK2

selon PEP 492 .

Fondamentalement, cela signifie que vous devriez faire:

async for number in generate(10):
        print(number)

Vérifiez également les Différences par rapport aux générateurs :

Les objets coroutine natifs n'implémentent pas les méthodes iter et next . Par conséquent, ils ne peuvent pas être itérés ou passés à iter (), list (), Tuple () et à d'autres éléments intégrés. Ils ne peuvent pas non plus être utilisés dans une boucle for..in. Une tentative d'utilisation de iter ou next sur un objet coroutine natif entraînera dans une TypeError.

5
bosnjak

J'ai utilisé cela pour asynchroniser une boucle dans une liste

class AsyncRange(object):
    def __init__(self, length):
        self.length = length
        self.i = 0

    async def __aiter__(self):
        return self

    async def __anext__(self):
        index = self.i
        self.i += 1
        if self.i <= self.length:
            return index
        else:
            raise StopAsyncIteration

puis simplement:

async for i in AsyncRange(my_list):
    # your code
1
Juggernaut