Frage

Going by the tornado.gen documentation can someone help me understand the exact difference between tornado.gen.coroutine and tornado.gen.engine

War es hilfreich?

Lösung

As the tornado documentation for gen.engine says:

This decorator is similar to coroutine, except it does not return a Future and the callback argument is not treated specially.

And as the gen.coroutine documentation says

From the caller’s perspective, @gen.coroutine is similar to the combination of @return_future and @gen.engine.

gen.engine is basically an older, less streamlined version of what coroutine does. If you're writing new code, you should follow the documentation's advice and always use tornado.gen.coroutine.

It's pretty evident if you look at the code for both functions (with documentation stripped out).

engine:

def engine(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        runner = None

        def handle_exception(typ, value, tb):
            if runner is not None:
                return runner.handle_exception(typ, value, tb)
            return False
        with ExceptionStackContext(handle_exception) as deactivate:
            try:
                result = func(*args, **kwargs)
            except (Return, StopIteration) as e:
                result = getattr(e, 'value', None)
            else:
                if isinstance(result, types.GeneratorType):
                    def final_callback(value):
                        if value is not None:
                            raise ReturnValueIgnoredError(
                                "@gen.engine functions cannot return values: "
                                "%r" % (value,))
                        assert value is None
                        deactivate()
                    runner = Runner(result, final_callback)
                    runner.run()
                    return
            if result is not None:
                raise ReturnValueIgnoredError(
                    "@gen.engine functions cannot return values: %r" %
                    (result,))
            deactivate()
            # no yield, so we're done
    return wrapper

coroutine:

def coroutine(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        runner = None
        future = TracebackFuture()

        if 'callback' in kwargs:
            callback = kwargs.pop('callback')
            IOLoop.current().add_future(
                future, lambda future: callback(future.result()))

        def handle_exception(typ, value, tb):
            try:
                if runner is not None and runner.handle_exception(typ, value, tb):
                    return True
            except Exception:
                typ, value, tb = sys.exc_info()
            future.set_exc_info((typ, value, tb))
            return True
        with ExceptionStackContext(handle_exception) as deactivate:
            try:
                result = func(*args, **kwargs)
            except (Return, StopIteration) as e:
                result = getattr(e, 'value', None)
            except Exception:
                deactivate()
                future.set_exc_info(sys.exc_info())
                return future
            else:
                if isinstance(result, types.GeneratorType):
                    def final_callback(value):
                        deactivate()
                        future.set_result(value)
                    runner = Runner(result, final_callback)
                    runner.run()
                    return future
            deactivate()
            future.set_result(result)
        return future
    return wrapper

Both of these are probably pretty hard to understand at first glance. But still, it's obvious that the code is very similar, except that @gen.coroutine has some special handling of the callback kwarg, and it builds/returns a Future. @gen.engine has some code that specifically throws an error if you try to return something from it, rather than putting it in the Future.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top