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

时间:2019-06-03 14:45:51

标签: python python-3.x python-2.7 exception raise

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

这会导致我在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

那么“最后一个...活动”是什么意思?是否有一些有关此重大更改的文档?还是这是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

我正在考虑切换到该位置...

2 个答案:

答案 0 :(得分:4)

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

  

在处理一个异常(异常A)期间,可能会发生另一异常(异常B)。在当今的Python(2.4版)中,如果发生这种情况,则异常B向外传播,而异常A丢失。

这正是您在2.7中看到的内容:预期(A)丢失是因为出现了意外(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

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

答案 1 :(得分:0)

raise使用与sys.exc_info相同的信息,后者记录both behaviors。由于已记录了变通办法利用的每帧行为,所以这是要走的路。

PEP 3110except语句进行了几处更改。我相信它包含了这一点,但唯一明确提到的是,as所存储的异常在离开except时会被丢弃。