Python 'raise' 没有参数:什么是 "the last exception that was active in the current scope"?

Python 'raise' without arguments: what is "the last exception that was active in the current scope"?

Python 的文档说:

If no expressions are present, raise re-raises the last exception that was active in the current scope.

(Python 3: https://docs.python.org/3/reference/simple_stmts.html#raise; Python 2.7: https://docs.python.org/2.7/reference/simple_stmts.html#raise.)

不过,"last active"的观念似乎变了。见证以下代码示例:

#
from __future__ import print_function
import sys
print('Python version =', sys.version)

try:
    raise Exception('EXPECTED')
except:
    try:
        raise Exception('UNEXPECTED')
    except:
        pass
    raise # re-raises UNEXPECTED for Python 2, and re-raises EXPECTED for Python 3

结果是 Python 2:

我没想到的结果
Python version = 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 10, in <module>
    raise Exception('UNEXPECTED')
Exception: UNEXPECTED

但是 Python 3:

得到了预期的(我的)结果
Python version = 3.6.8 (default, Feb 14 2019, 22:09:48)
[GCC 7.4.0]
Traceback (most recent call last):
  File "./x", line 7, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

Python version = 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 7, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

那么"the last ... active"是什么意思呢?是否有关于此重大更改的一些文档?或者这是一个 Python 2 错误?

更重要的是:在 Python 2 中实现此功能的最佳方法是什么? (最好让代码在 Python 3 中继续工作。)


注意如果把代码改成

#
from __future__ import print_function
import sys
print('Python version =', sys.version)

def f():
    try:
        raise Exception('UNEXPECTED')
    except:
        pass

try:
    raise Exception('EXPECTED')
except:
    f()
    raise # always raises EXPECTED

然后 Python 2 也开始工作了:

Python version = 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 13, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

我正在考虑改用那个...

raise 使用与 sys.exc_info 相同的信息,记录 both behaviors。由于记录了您的变通方法利用的 per-frame 行为,这就是要走的路。

PEP 3110except 语句进行了一些更改。我相信它包括这个,但唯一明确提到的是 as 存储的异常在离开 except.

时被丢弃

Python 2 的行为与其说是 错误 ,不如说是 设计缺陷 。 Python 3.0 通过添加异常链接功能解决了这个问题。可以在 PEP 3134 -- Exception Chaining and Embedded Tracebacks 中找到最接近此更改的文档 动机:

During the handling of one exception (exception A), it is possible that another exception (exception B) may occur. In today's Python (version 2.4), if this happens, exception B is propagated outward and exception A is lost.

这正是您在 2.7 中看到的:EXPECTED (A) 丢失,因为 UNEXPECTED (B) 出现并覆盖了它。使用 Python 3 中较新的异常链接功能,可以通过异常实例上的 __cause____context__ 属性保留两个错误的完整上下文。

对于更直接的 cross-compatible 解决方法,我鼓励您手动保留引用,明确显示 re-raised 是哪个错误,并且像往常一样避免裸露的 except 语句(总是太宽泛):

try:
    raise Exception('EXPECTED')
except Exception as err_expected:
    try:
        raise Exception('UNEXPECTED')
    except Exception as err_unexpected:
        pass
    raise err_expected

如果您希望以 cross-compatible 的方式抑制 exception-chaining 功能,您可以通过在 re-raising.

之前设置 err_expected.__cause__ = None 来实现