Python不带参数的“引发”:什么是“当前作用域中活动的最后一个异常”?

Mar*_*ica 10 python exception raise python-2.7 python-3.x

Python的文档说:

如果不存在任何表达式,请raise重新引发当前作用域中活动的最后一个异常。

(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。)

但是,“上次激活”的概念似乎已经改变。见证以下代码示例:

#
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
Run Code Online (Sandbox Code Playgroud)

这会导致我在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
Run Code Online (Sandbox Code Playgroud)

但使用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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

那么“最后一个...活动”是什么意思?是否有一些有关此重大更改的文档?还是这是Python 2错误?

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


请注意,如果将代码更改为

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
Run Code Online (Sandbox Code Playgroud)

然后事情也开始适用于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
Run Code Online (Sandbox Code Playgroud)

我正在考虑改用...

wim*_*wim 5

Python 2 的行为与其说是错误,不如说是设计缺陷。Python 3.0 通过添加异常链功能解决了这个问题。与此更改的文档最接近的内容可以在PEP 3134 — 异常链和嵌入式回溯 动机中找到:

在处理一个异常(异常A)的过程中,有可能发生另一异常(异常B)。在今天的Python(版本2.4)中,如果发生这种情况,异常B会向外传播,异常A会丢失。

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

对于更直接的交叉兼容解决方法,我鼓励您手动保留引用,明确显示正在重新引发的错误,并像往常一样避免裸except语句(它们总是太宽泛):

try:
    raise Exception('EXPECTED')
except Exception as err_expected:
    try:
        raise Exception('UNEXPECTED')
    except Exception as err_unexpected:
        pass
    raise err_expected
Run Code Online (Sandbox Code Playgroud)

如果您希望以交叉兼容的方式抑制异常链接功能,可以通过err_expected.__cause__ = None在重新引发之前进行设置来实现。