python - 用另一个函数包装 tornado.gen.engine-wrapped 函数

标签 python asynchronous generator tornado

比如说,我有一个函数,它用 gen.engine 包裹起来以“理顺”回调链,也就是说,这样代码看起来是同步的/线性的/其他的。

函数,然后看起来像这样

@gen.engine
def func():
     ...
     yield gen.Task(...)
     ...
     yield gen.Task(...)

等等。我明白,我绝对可以在 yield 周围使用 try/except 来捕获函数中发生的异常,这些异常由 gen.Task 包装。如果我需要将函数 func 本身包装在另一个函数中以(这是实际用例)捕获 func 中的所有“未捕获”异常而不引入“丑陋”怎么办(对..)try/except,这将跨越整个func

我想出了这个:

@gen.engine
def func(..., callback):
     ...
     callback()

@gen.engine
def outer():
    try:
        yield gen.Task(func)
    except Exception as e:
        # Log the exception
    # Stop ioloop (or something)

这为 func 增加了一点通用性,但在 func 中引入了一个额外的参数和一些人工逻辑。

还有其他方法吗?请注意,“紧急异常捕获”或多或少是出于此问题目的的人为用例(这可能可以通过其他方式完成),我正在寻找的是调用那些 tornado.gen 的正确方法。来自另一个函数的引擎包装函数。

编辑:愚蠢的我,我应该提到我仅限于 tornado 2.x!

最佳答案

@gen.coroutine 是 Tornado 3 的新特性。来自 http://www.tornadoweb.org/en/stable/releases/v3.0.0.html :

New decorator @gen.coroutine is available as an alternative to @gen.engine. It automatically returns a Future, and within the function instead of calling a callback you return a value with raise gen.Return(value) (or simply return value in Python 3.3).

来自文档(http://www.tornadoweb.org/en/stable/gen.html#tornado.gen.coroutine):

Functions with this decorator return a Future. Additionally, they may be called with a callback keyword argument, which will be invoked with the future’s result when it resolves. If the coroutine fails, the callback will not be run and an exception will be raised into the surrounding StackContext. The callback argument is not visible inside the decorated function; it is handled by the decorator itself.

因此没有理由担心回调,也没有必要将函数包装到 tornado.gen.Task() 中。链接现在很容易:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import logging

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class MainHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def outer(self):
        logging.info('outer starts')
        yield self.inner()
        yield self.inner()  
        logging.info('outer ends')  
        raise tornado.gen.Return('hello')

    @tornado.gen.coroutine
    def inner(self):
        logging.info('inner runs')

    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        res = yield self.outer()
        self.write(res)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/", MainHandler)])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

输出:

$ python test.py 
[I 130529 03:18:35 test:21] outer starts
[I 130529 03:18:35 test:29] inner runs
[I 130529 03:18:35 test:29] inner runs
[I 130529 03:18:35 test:24] outer ends
[I 130529 03:18:35 web:1514] 200 GET / (127.0.0.1) 1.48ms

从 Python 3.3 开始,无需使用 gen.Result(),简单的 return 即可。在旧版本中,会出现 'return' with argument inside generator 错误。

另外,检查:https://github.com/facebook/tornado/issues/759

更新:

至于 Tornado 2.x,我认为没有简单的方法来隐藏回调。文档状态:

In most cases, functions decorated with engine should take a callback argument and invoke it with their result when they are finished. One notable exception is the RequestHandler get/post/etc methods, which use self.finish() in place of a callback argument.

所以我担心这些是不可避免的。示例:

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.engine
    def get(self):
        res = yield tornado.gen.Task(self.outer)
        self.write(res)
        self.finish()

    def inner(self, callback):
        logging.info('inner runs')
        callback()

    @tornado.gen.engine
    def outer(self, callback):
        logging.info('outer starts')
        yield tornado.gen.Task(self.inner)
        yield tornado.gen.Task(self.inner)
        logging.info('outer ends')
        callback("hello")

关于python - 用另一个函数包装 tornado.gen.engine-wrapped 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16785045/

相关文章:

c++ - 在 C++ 中更新 vector 或列表的多个调用异步

python - 我可以加入类(class)列表吗?

python - 比较不同类型时如何强制 Python 2 引发异常?

python - PyQt - QFileDialog - 直接浏览到文件夹?

c# - 在 C# 中创建非阻塞异步门

javascript - 在使用 javascript 单击按钮时不使用生成器函数即可获取序列中的下一个值

python - 如何定义一个空的生成器函数?

python在数组组和中嵌套字典

python - 打开 Python 串行端口时出错

angularjs - 指令未使用 controllerAs 和 bindToController 使用异步数据更新 View