2014-05-17 23 views
8

Ich habe vor kurzem mit asyncio herum Ausmisten, und während ich eine Intuition zu bekommen fange an, wie es funktioniert, ist es etwas, das ich habe zu tun, nicht in der Lage gewesen. Ich bin nicht sicher, ob es daran liegt, dass ich die Konstruktion falsch verstanden habe, oder wenn es einen Grund gibt, warum das, was ich versuche, keinen Sinn ergibt.Iterieren über asyncio.coroutine

Kurz gesagt, ich möchte in der Lage sein, ein Nachgeben asyncio.coroutine iterieren. Zum Beispiel würde Ich mag Lage sein, etwas zu tun:

@asyncio.coroutine 
def countdown(n): 
    while n > 0: 
     yield from asyncio.sleep(1) 
     n = n - 1 
     yield n 

@asyncio.coroutine 
def do_work(): 
    for n in countdown(5): 
     print(n) 

loop.run_until_complete(do_work()) 

Allerdings wirft dies eine Ausnahme aus den Eingeweiden von asyncio. Ich habe andere Dinge ausprobiert, wie for n in (yield from countdown(5)): ..., aber das gibt auch eine ähnlich undurchsichtige Laufzeit Ausnahme.

Ich kann nicht sofort sehen, warum Sie nicht so etwas tun sollte, aber ich bin an die Grenzen meiner Fähigkeit bekommen, zu verstehen, was los ist.

So:

  • wenn es möglich ist, dies zu tun, wie kann ich es tun?
  • wenn es nicht möglich ist, warum nicht?

Lassen Sie mich wissen, ob diese Frage nicht klar ist!

Antwort

5

In asyncio-Coroutinen sollten Sie yield from und niemals yield verwenden. Das ist von Entwurf. Argument für yield from sollte nur eine andere Koroutine oder asyncio.Future Instanz sein.

Aufrufe von Coroutine selbst sollten mit yield from wieder wie yield from countdown(5) verwendet werden.

Für Ihren Fall empfehle ich mit Warteschlangen:

import asyncio 

@asyncio.coroutine 
def countdown(n, queue): 
    while n > 0: 
     yield from asyncio.sleep(1) 
     n = n - 1 
     yield from queue.put(n) 
    yield from queue.put(None) 

@asyncio.coroutine 
def do_work(): 
    queue = asyncio.Queue() 
    asyncio.async(countdown(5, queue)) 
    while True: 
     v = yield from queue.get() 
     if v: 
      print(v) 
     else: 
      break 

asyncio.get_event_loop().run_until_complete(do_work()) 

Nun können Sie für Werte von countdown ergab überprüfen, wird das folgende Beispiel funktioniert. Aber ich denke, es ist Antipattern:

  1. Zu einfach ein Chaos

  2. Sie sowieso nicht countdown Anrufe mit, sagen wir, itertools Funktionen zusammenstellen zu lassen. Ich meine etwas wie sum(countdown(5)) oder itertools.accumulate(countdown(5)).

Wie auch immer, zB mit yield und yield from in Koroutine Mischen:

import asyncio 

@asyncio.coroutine 
def countdown(n): 
    while n > 0: 
     yield from asyncio.sleep(1) 
     n = n - 1 
     yield n 

@asyncio.coroutine 
def do_work(): 
    for n in countdown(5): 
     if isinstance(n, asyncio.Future): 
      yield from n 
     else: 
      print(n) 

asyncio.get_event_loop().run_until_complete(do_work()) 
1

Update: Es scheint, Python 3.5 supports this even better natively:

mit dem gleichen Problem stecken (und Code inspiriert in aio-s3), ich fühlte, dass es eine elegantere Lösung geben sollte.

import asyncio 

def countdown(number): 
    @asyncio.coroutine 
    def sleep(returnvalue): 
     yield from asyncio.sleep(1) 
     return returnvalue 
    for n in range(number, 0, -1): 
     yield sleep(n) 

@asyncio.coroutine 
def print_countdown(): 
    for future in countdown(5): 
     n = yield from future 
     print ("Counting down: %d" % n) 

asyncio.get_event_loop().run_until_complete(print_countdown()) 

Rationale: Die Methode liefert countdown Futures, die jeweils nach einer 1 Sekunde Schlaf auf die Anzahl vorgesehen lösen wird.

Die Funktion print_countdown nimmt die erste Zukunft, yield from-es (die bis zur Auflösung pausiert) und erhalten das beabsichtigte Ergebnis: n.

4

In Python 3.5 wird die async for Syntax eingeführt. Die asynchrone Iteratorfunktionssyntax ist jedoch immer noch nicht vorhanden (d. H. yield ist in async Funktionen verboten). Hier ist eine Abhilfe:

import asyncio 
import inspect 

class escape(object): 
    def __init__(self, value): 
     self.value = value 

class _asynciter(object): 
    def __init__(self, iterator): 
     self.itr = iterator 
    async def __aiter__(self): 
     return self 
    async def __anext__(self): 
     try: 
      yielded = next(self.itr) 
      while inspect.isawaitable(yielded): 
       try: 
        result = await yielded 
       except Exception as e: 
        yielded = self.itr.throw(e) 
       else: 
        yielded = self.itr.send(result) 
      else: 
       if isinstance(yielded, escape): 
        return yielded.value 
       else: 
        return yielded 
     except StopIteration: 
      raise StopAsyncIteration 

def asynciter(f): 
    return lambda *arg, **kwarg: _asynciter(f(*arg, **kwarg)) 

Dann könnte Ihr Code geschrieben werden als:

@asynciter 
def countdown(n): 
    while n > 0: 
     yield from asyncio.sleep(1) 
     #or: 
     #yield asyncio.sleep(1) 
     n = n - 1 
     yield n 

async def do_work(): 
    async for n in countdown(5): 
     print(n) 

asyncio.get_event_loop().run_until_complete(do_work()) 

über die neue Syntax zu lernen, und wie dieser Code funktioniert, finden Sie PEP 492

+0

Das ist genial! Natürlich sollten wir Python 3.6 verwenden, wo Asyncgeneratoren nativ unterstützt werden, aber wenn Sie aus irgendeinem Grund zu Python 3.5 gezwungen werden, ist dies ein echtes Juwel. Es ist auch sehr ähnlich in der Syntax, wie Sie Async-Generatoren in 3.6 verwenden würden, so sollte der Übergang einfach sein, wenn es passiert. –

+0

Wie kann ich verschachtelte Async-Generatoren auf diese Weise erstellen? Was wäre in Ihrem Beispiel, wenn 'do_work' selbst Ergebnisse liefern sollte? Wenn wir es mit '@ asynciter' anstatt mit' async def' versehen, können wir 'async for' natürlich nicht mehr verwenden. Wie iterieren wir über die Ergebnisse der inneren Coroutine? –

+0

Eigentlich, vergiss es, ich habe entdeckt, dass es ein tolles Paket [async_generator] (https://pypi.python.org/pypi/async_generator/1.7) gibt, das all das schon macht! –