Reputation: 23637
yield
cannot be used in an async def
function to suspend execution and return to the caller. Is there a different way to do this in Python 3.5?
Edit: Answers to a very similar question suggest either to use Python 3.6 or to define an asynchronous generator as a class. Unfortunatley, the former is not an option at the moment. The generator could work but defining a class for this purpose seems to be more clunky than my own attempt.
Presumably, there is no clean and easy solution?
I need to write functions that can suspend their operation, return to their caller, and continue later where they stopped. These could be implemented as state machines, possibly using classes, which tears apart relevant code and is generally rather unreadable. With generators, Python offers a more or less elegant way to write such functions:
def a():
print('A Part 1')
yield # suspend execution to caller
print('A Part 2')
def run(func):
o = func()
try:
while True:
print('running...')
o.send(None)
except StopIteration:
pass
run(a)
# running...
# A Part 1
# running...
# A Part 2
Python 3.3 added the yield from
syntax, which allows nice nesting of such functions. Instead of returning execution to the caller, yield from
transfers execution to another function:
def b():
print('B Part 1')
yield from a()
print('B Part 2')
run(b)
# running...
# B Part 1
# A Part 1
# running...
# A Part 2
# B Part 2
Python 3.5 introduced async def
and await
to distinguish coroutines from generators. Of course I would rather use these native coroutines. It is easy to rewrite b
, simply by replacing def
with async def
and yield from
with await
. However, I did not find a canonical way to suspend a coroutine and return to the caller. yield
is not allowed in an async function, and await only takes another function to run. I came up with this awkward solution:
import asyncio
@asyncio.coroutine
def awkward_suspend():
yield
async def c():
print('C Part 1')
#yield # SyntaxError: 'yield' inside async function
#await # Syntax Error: invalid syntax
await awkward_suspend()
print('C Part 2')
run(c)
# running...
# C Part 1
# running...
# C Part 2
This approach wraps yield
in a normal function, thus creating a generator, and marks the generator as a coroutine so it can be await
ed.
This feels very much like an abuse of the language. Is there a way to achieve the same result without asyncio
and the awkward suspend function?
Upvotes: 3
Views: 943
Reputation: 4209
You can instead simply use sleep(0):
async def c():
print('C Part 1')
await asyncio.sleep(0)
print('C Part 2')
sleep(0) does exactly the awkward_suspend - a simple yield In fact they have a private __sleep0() function there with that yield statement.
Upvotes: 2