What is the correct way to yield from a stream?
Because StreamReader.read
is a coroutine, your only options for calling it are a) wrapping it in a Task
or Future
and running that via an event loop, b) await
ing it from coroutine defined with async def
, or c) using yield from
with it from a coroutine defined as a function decorated with @asyncio.coroutine
.
Since Connection.read
is called from an event loop (via the coroutine new_connection
), you can't reuse that event loop to run a Task
or Future
for StreamReader.read
: event loops can't be started while they're already running. You'd either have to stop the event loop (disastrous and probably not possible to do correctly) or create a new event loop (messy and defeating the purpose of using coroutines). Neither of those are desirable, so Connection.read
needs to be a coroutine or an async
function.
The other two options (await
in an async def
coroutine or yield from
in a @asyncio.coroutine
-decorated function) are mostly equivalent. The only difference is that async def
and await
were added in Python 3.5, so for 3.4, using yield from
and @asyncio.coroutine
is the only option (coroutines and asyncio
didn't exist prior to 3.4, so other versions are irrelevant). Personally, I prefer using async def
and await
, because defining coroutines with async def
is cleaner and clearer than with the decorator.
In brief: have Connection.read
and new_connection
be coroutines (using either the decorator or the async
keyword), and use await
(or yield from
) when calling other coroutines (await conn.read(4)
in new_connection
, and await self.__in.read(n_bytes)
in Connection.read
).
I found a chunk of the StreamReader source code on line 620 is actually a perfect example of the function's usage.
In my previous answer, I overlooked the fact that self.__in.read(n_bytes)
is not only a coroutine (which I should've known considering it was from the asyncio
module XD) but it yields a result on line . So it is in fact a generator, and you will need to yield from it.
Borrowing this loop from the source code, your read function should look something like this:
def read(self, n_bytes : int = -1): data = bytearray() #or whatever object you are looking for while 1: block = yield from self.__in.read(n_bytes) if not block: break data += block return data
Because self.__in.read(n_bytes)
is a generator, you have to continue to yield from it until it yields an empty result to signal the end of the read. Now your read function should return data rather than a generator. You won't have to yield from this version of conn.read()
.