【问题标题】:Python, Tornado: gen.coroutine decorator breaks try-catch in another decoratorPython,Tornado:gen.coroutine 装饰器在另一个装饰器中破坏了 try-catch
【发布时间】:2018-06-21 06:20:30
【问题描述】:

我有一个带有大量静态方法和 Tornado 协程装饰器的类。我想添加另一个装饰器,以捕获异常并将它们写入文件:

# my decorator
def lifesaver(func):
    def silenceit(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as ex:
            # collect info and format it
            res = ' ... '
            # writelog(res)
            print(res)
            return None

    return silenceit

但是,它不适用于 gen.coroutine 装饰器:

class SomeClass:

    # This doesn't work!
    # I tried to pass decorators in different orders,
    # but got no result.
    @staticmethod
    @lifesaver
    @gen.coroutine
    @lifesaver
    def dosomething1():
        raise Exception("Test error!")


    # My decorator works well
    # if it is used without gen.coroutine.
    @staticmethod
    @gen.coroutine
    def dosomething2():
        SomeClass.dosomething3()

    @staticmethod
    @lifesaver
    def dosomething3():
        raise Exception("Test error!")

我知道,Tornado 使用了 raise Return(...) 方法,这可能是基于 Exceptions 的,也许它会以某种方式阻止 try-catch 其他装饰器......所以,如何使用我的装饰器来处理带有 Tornado 协程的异常

答案

感谢 Martijn Pieters,我得到了这段代码:

def lifesaver(func):
    def silenceit(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except (gen.Return, StopIteration):
            raise
        except Exception as ex:
            # collect info and format it
            res = ' ... '
            # writelog(res)
            print(res)

            raise gen.Return(b"")

    return silenceit

所以,我只需要指定 Tornado Return。我试图将@gen.coroutine 装饰器添加到silenceit 函数并在其中使用yield,但这会导致Future 对象的Future 对象和其他一些奇怪的不可预测的行为。

【问题讨论】:

    标签: python exception tornado decorator


    【解决方案1】:

    您正在装饰gen.coroutine 的输出,因为装饰器是从下到上应用的(因为它们从上到下相互嵌套)。

    与其装饰协程,不如装饰你的函数并将gen.coroutine装饰器应用于该结果:

    @gen.coroutine
    @lifesaver
    def dosomething1():
        raise Exception("Test error!")
    

    您的装饰器无法真正处理 @gen.coroutine 装饰函数产生的输出。 Tornado 依赖异常来传递结果(因为在 Python 2 中,生成器不能使用 return 来返回结果)。您需要确保通过 Tornado 所依赖的异常。你还应该重新包装你的包装函数:

    from tornado import gen
    
    def lifesaver(func):
        @gen.coroutine
        def silenceit(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except (gen.Return, StopIteration):
                raise
            except Exception as ex:
                # collect info and format it
                res = ' ... '
                # writelog(res)
                print(res)
                raise gen.Return(b"")
    
        return silenceit
    

    在异常情况下,会引发一个空的 Return() 对象;根据需要进行调整。

    帮自己一个忙,不要使用类,只需将staticmethod 函数放在那里。只需将这些函数放在模块的顶层。类用于组合方法和共享状态,而不是创建命名空间。改为使用模块来创建命名空间。

    【讨论】:

    • 感谢您的回复!我在Q中提到,我尝试了不同顺序的装饰器,甚至@lifesaver @gen.coroutine @lifesaver,但这仍然不起作用,请求返回500:内部服务器错误。
    • @AivanF.: 对,因为gen.coroutine 需要一个生成器,总是。我只是阅读了文档。
    • 我知道了,非常感谢!我会尝试声明函数而不是静态方法:)
    • @AivanF.:我更新了我的答案以更好地反映 Tornado 实施的现实。
    • 我尝试使用协程装饰器和yield,如您所示,但遇到了很多麻烦。但是,您对gen.Return 的使用足以解决问题。查看我的问题的更新。
    猜你喜欢
    • 2017-11-16
    • 2015-02-22
    • 2011-09-03
    • 1970-01-01
    • 2018-02-08
    • 2021-01-24
    • 1970-01-01
    • 1970-01-01
    • 2022-12-14
    相关资源
    最近更新 更多