2

我有一个使用 Python 3.6+ 的子类,__set_name__以确保拥有的类已经注释了携带子类的字段的类型。如果他们没有引发异常。

但是,任何引发的异常总是被 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确实是我的代码引发的异常?

4

2 回答 2

2

因此,由于您得到了整个“上述异常是以下异常的直接原因”,这意味着在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!')

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

这是文档的链接,它更详细地解释了这一点。

于 2020-05-27T11:14:22.540 回答
1

您可以访问__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
于 2020-05-27T11:28:12.517 回答