Python __set_name__ 中的异常行为

Exception behaviour in Python __set_name__

我有一个 child class 正在利用 Python 3.6+ __set_name__ 来确保拥有 classes 已经注释了字段的类型背着childclass。如果没有,则会引发异常。

但是,引发的任何异常总是被 Python 捕获,而引发 RuntimeError

例如:

class Child:
    def __set_name__(self, owner, name):
        raise Exception("OOPS!")

class Owner():
    child = Child()

结果:

Traceback (most recent call last):
  File "<stdin>", line 3, in __set_name__
Exception: OOPS!

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Error calling __set_name__ on 'Child' instance 'child' in 'Owner'

这很可能是预期的行为(无法找到对 __set_name__ 异常的具体引用),但也可能表明预期是 __set_name__ 永远不会出现异常。

如果在正确的条件下发生异常,我看到的行为不是问题。但是,鉴于我无法确定引发的异常是我的代码引发的异常,因此测试起来很棘手。

是否有更好的方法来引发异常以供测试,或者确实有一种简单的方法来检查 RuntimeError 包装的异常是否确实是我的代码引发的异常?

因此,由于您得到的是整个 "The above exception was the direct cause of the following exception",这意味着 type(基本元类)中的某处基本上有以下效果:

try:
    descr.__set_name__(A, 'attr')
except Exception as e:
    raise RuntimeError(msg) from e

也就是说,它正在使用 raise new_exception from original_exception,因此您应该能够使用 __cause__ 属性反省原始异常是什么:

所以,观察:

In [1]: class Child:
   ...:     def __set_name__(self, owner, name):
   ...:         raise Exception("OOPS!")
   ...: try:
   ...:     class Owner():
   ...:         child = Child()
   ...: except RuntimeError as e:
   ...:     err = e
   ...:

In [2]: err
Out[2]: RuntimeError("Error calling __set_name__ on 'Child' instance 'child' in 'Owner'")

In [3]: err.__cause__
Out[3]: Exception('OOPS!')

同样,我认为这些都没有记录在案,因此您可能依赖于实现细节。

Here is a link to the documentation 更详细地解释了这一点。

您可以访问包装异常的 __cause__ 属性来检查是否由于您引发的异常而发生这种情况:

try:
    class Child:
        def __set_name__(self, owner, name):
            raise Exception("OOPS!")

    class Owner():
        child = Child()

except RuntimeError as rte:
    assert rte.__cause__.args[0] == "OOPS!"  # or a more appropriate check