如何避免使用 sys.exc_clear

how to avoid to use sys.exc_clear

这是我的代码:

import sys

class App(object):

    def __enter__(self):
         return self

    def __exit__(self, exc_type, exc_value, tb):
         print sys.exc_info()

app = App()

with app:

    try:
        1/0
    except:
        print  'Caught you'
        #sys.exc_clear()

另一个使用 Flask 应用上下文的例子:

from flask import Flask

app = Flask(__name__)


@app.teardown_appcontext
def teardown(exception):
    print exception


with app.app_context():

    try:
        1 / 0
    except:
        pass

奇怪的是,如果我在处理异常时没有调用 sys.exc_clear,那么当退出应用程序时,sys.exc_info 仍然会 return 异常信息。

有什么办法可以避免这种情况吗?

在我基于flask的项目中,当出现异常时,我会回滚事务。虽然我处理了异常,但应用程序上下文仍然可以使用 sys.exc_info 获取它,如下面的代码所示:

#AppContext pop method

def pop(self, exc=None):
    """Pops the app context."""
    self._refcnt -= 1
    if self._refcnt <= 0:
        if exc is None:
            exc = sys.exc_info()[1]
        self.app.do_teardown_appcontext(exc)
    rv = _app_ctx_stack.pop()
    assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \
        % (rv, self)
    appcontext_popped.send(self.app)

我不能要求团队中的每个人在处理每一个异常时都调用 sys.exc_info。

我应该怎么做才能避免这种情况?

这确实是 AppContext 处理您的情况的一个错误。

异常在当前帧退出时自动清除(因此在 with 语句之外)。如果您从 with 语句框架内调用另一个函数并在那里处理异常,则当另一个函数退出时,sys.exc_info() 信息将再次被清除。

AppContext.__exit__() 方法被正确通知您处理了异常并将异常值传递给 AppContext.pop() 方法。

因此,Flask AppContext.pop() 方法应该使用 不同的 标记值来检测没有传入异常;它可以检测到 None 不是作为默认值而是作为实际值传入的,表明没有引发异常或未正确处理。

filed an issue with the project requesting that this is implemented, with accompanying pull request。这已合并并将成为 Flask 未来版本的一部分。

您可以使用 monkeypatch 将其反向移植到当前的 Flask 版本:

from flask import app, ctx
import sys

if ctx.AppContext.pop.__defaults__ == (None,):
    # unpatched
    _sentinel = object()

    def do_teardown_appcontext(self, exc=_sentinel):
        if exc is _sentinel:
            exc = sys.exc_info()[1]
        for func in reversed(self.teardown_appcontext_funcs):
            func(exc)
        app.appcontext_tearing_down.send(self, exc=exc)

    app.Flask.do_teardown_appcontext = do_teardown_appcontext

    def pop(self, exc=_sentinel):
        """Pops the app context."""
        self._refcnt -= 1
        if self._refcnt <= 0:
            if exc is _sentinel:
                exc = sys.exc_info()[1]
            self.app.do_teardown_appcontext(exc)
        rv = ctx._app_ctx_stack.pop()
        assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \
            % (rv, self)
        ctx.appcontext_popped.send(self.app)

    ctx.AppContext.pop = pop