为什么不重新抛出 try-except-else 中 else 内部的异常?

Why are exceptions inside else in try-except-else not re-thrown?

我在 python 中使用 try|except|else|finally。 如果 else 中的代码抛出异常,我希望我的整个脚本失败(在执行 finally 之后)。 我发现这没有发生。 else 内的异常正在被抑制。为什么?

MWE

import requests
def foo():
    try:
        resp = requests.get("https://example.com")
        status = resp.status_code
        assert resp.status_code < 300, "Bad status code"
    except requests.exceptions.BaseHTTPError:
        status = None
    else:
        print("Starting else branch")

        # this should fail
        # because the body is not json
        print(f"Response body was {resp.json()}")

        print("Finishing else branch")
    finally:
        # save the result persistently,
        # regardless of if it was good or bad
        with open('log.txt', 'w') as f:
            f.write(str(status))
        return status

print(f"foo() gracefully returned {foo()}")

我试过 python 3.6.9、3.9.5 和 3.8。

期望的行为:

resp.json()else 中抛出 simplejson.errors.JSONDecodeError 异常,然后被捕获,finally 是 运行,但是 [=13] 中的异常=] 被 重新抛出 ,因此整个脚本因 JSONDecodeError.

而失败

stdout 显示:

starting else branch

然后抛出异常,回溯到resp.json().

The python docs 对于 try|except|else|finally 中的 else 说:

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.

所以听起来 else 子句的目的是将代码放在您需要的地方 而不是 希望捕获异常。这就是我想要的。但似乎 else 中的异常被 捕获。

(请注意,在这个例子中,我希望 AssertionError 导致整个函数失败,并且 BaseHTTPError 被捕获并优雅地处理。这对于实际用例来说并没有多大意义,但这只是一个简单的 MWE。)

实际行为

stdout 说:

Starting else branch

foo() gracefully returned 200

捕获else里面的JSONDecodeError异常,执行finally,代码returns200。即捕获了else代码中的异常。因此,else 分支 中的代码似乎受到 try.

的保护

如果我想让 JSONDecodeError 被捕获到我的 .json() 行,我会把它放在 try 里面。 如何让此脚本 捕获 JSONDecodeError 错误?

您所看到的内容已得到完美记录:

If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause. If the finally clause raises another exception, the saved exception is set as the context of the new exception. If the finally clause executes a return, break or continue statement, the saved exception is discarded:

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
...
>>> f()
42

https://docs.python.org/3/reference/compound_stmts.html#try

要在 finally 执行后在 else 内抛出异常,您不能在 finally 内有 return。取消缩进以将其移出 finally 分支。