Search code examples
pythongeneratortornadoyield

Difference between "yield" of Tornado and "yield from" of asyncio in mechanism?


In Tornado, we usually write the following code to call a function asynchronously:

class MainHandler(tornado.web.RequestHandler):

    @tornado.gen.coroutine
    def post(self):
        ...
        yield self.handleRequest(foo)
        ...

    @tornado.gen.coroutine
    def handleRequest(self, foo):
        ...

But in asyncio (will be shipped with Python 3.4, can be installed from pip for Python 3.3), we use yield from to achieve the same thing:

@asyncio.coroutine
def myPostHandler():
    ...
    yield from handleRequest(foo)
    ...


@asyncio.coroutine
def handleRequest(foo)
    ...

Seeing from the code, the difference is yield and yield from. However the former handleRequest(foo) returns a tornado.concurrent.Future object, the latter returns a generator object.

My question is, what is the difference between the two things in mechanism? How is the control flow? And who calls the actual handleRequest and retrieves its returning value?

Append: I have basic knowledge of Python generators and iterators. I wanted to understand what Tornado and asyncio achieved by using these, and what is the difference between those two mechanisms.


Solution

  • There is a huge difference between the two. yield from takes another generator and continues yielding from that generator instead (delegating responsibility, as it were). yield just yields one value.

    In other words, yield from, in the simplest case, could be replaced by:

    for value in self.handleRequest(foo):
        yield value
    

    If you replaced a yield from <expression> line with yield <expression> you'd return the whole generator to the caller, not the values that generator produces.

    The yield from syntax was only introduced in Python 3.3, see PEP 380: Syntax for Delegating to a Subgenerator. Tornado supports Python versions 2.6, 2.7 and 3.2 in addition to Python 3.3, so it cannot rely on the yield from syntax being available. asyncio, on the other hand, being a core Python library added in 3.4, can fully rely on the yield from generator delegation syntax being available.

    As a result, Tornado will have to post-process values yielded from a @tornado.gen.coroutine generator to detect that a tornado.concurrent.Future object was yielded; the @asyncio.coroutine code handling can be much simpler. And indeed the Tornado Runner.run() method does explicit type checks to handle delegated tasks.