Regarding #1: Python does no such thing. Note that the fast_sqrt
function you've written (i.e. before any decorators) is not a generator function, coroutine function, task, or whatever you want to call it. It's an ordinary function running synchronously and returning what you write after the return
statement. Depending on the presence of @coroutine
, very different things happen. It's just bad luck that both result the same error.
Without the decorator,
fast_sqrt(x)
runs like the ordinary function it is and returns a future of a float (regardless of context). That future is consumed by thefuture = yield from ...
, leavingfuture
a float (which doesn't have aresult
method).With the decorator, the call
f(x)
goes through a wrapper function created by@coroutine
. This wrapper function callsfast_sqrt
and unpacks the resulting future for you, using theyield from <future>
construction. Therefore, this wrapper function is itself a coroutine. Therefore,future = yield from ...
waits on that coroutine and leavesfuture
a float, again.
Regarding #2, yield from <future>
does work (as explained above, you're using it when using the undecorated fast_sqrt
), and you could also write:
future = yield from coro_returning_a_future(x)
res = yield from future
(Modulo that it doesn't work for fast_sqrt
as written, and gains you no extra async-ness because the future is already done by the time it's returned from coro_returning_a_future
.)
Your core problem seems to be that you confuse coroutines and futures. Both your sqrt implementations try to be async tasks resulting in futures.
From my limited experience, that's not how one usually writes asyncio code. It allows you to pull both the construction of the future and the computation which the future stands for into two independent async tasks. But you don't do that (you return an already-finished future). And most of the time, this is not a useful concept: If you have to do some computation asynchronously, you either write it as a coroutine (which can be suspended) or you push it into another thread and communicate with it using yield from <future>
. Not both.
To make the square root computation async, just write a regular coroutine doing the computation and return
the result (the coroutine
decorator will turn fast_sqrt
into a task that runs asynchronously and can be waited on).
@coroutine
def fast_sqrt(x):
if x >= 0:
return math.sqrt(x)
else:
raise Exception("negative number")
@coroutine # for documentation, not strictly necessary
def slow_sqrt(x):
yield from asyncio.sleep(1)
if x >= 0:
return math.sqrt(x)
else:
raise Exception("negative number")
...
res = yield from f(x)
assert isinstance(res, float)