运行 `python -m unittest` 改变了带有覆盖的 `__name__` 的异常在回溯中打印的方式

Running `python -m unittest` changes the way exceptions with overriden `__name__` are printed in the traceback

我有一段动态创建 Exceptions 的代码。以这种方式创建的每个异常 class 都会被其 __name__ 覆盖:

def exception_injector(name, parent, module_dict):
    class product_exception(parent):
        pass
    product_exception.__name__ = name
    product_exception.__module__ = module_dict["__name__"]
    module_dict[name] = product_exception

当我经常使用它时,它可以很好地打印出所有内容:

>>> namespace = {"__name__": "some.module"}
>>> exception_injector("TestError", Exception, namespace)
>>> raise namespace["TestError"]("What's going on?")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
some.module.TestError: What's going on?

但是当我使用 unittest 模块时,原始的 __name__ 被打印出来:

>>> import unittest
>>> class DemoTestCase(unittest.TestCase):
...     def test_raise(self):
...         namespace = {"__name__": "some.module"}
...         exception_injector("TestError", Exception, namespace)
...         namespace["TestError"]("What's going on?")
...
>>> unittest.main(defaultTest="DemoTestCase", argv=["demo"], exit=False)
E
======================================================================
ERROR: test_raise (__main__.DemoTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 3, in test_raise
some.module.exception_injector.<locals>.product_exception: What's going on?

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
<unittest.main.TestProgram object at 0x1017c3eb8>

unittest 模块可以从异常对象的何处获取此原始信息?

单元测试使用 traceback module 格式化异常。您可以通过执行相同的操作来重现您的输出:

>>> import traceback
>>> try:
...     raise namespace["TestError"]("What's going on?")
... except Exception as e:
...     tb = traceback.TracebackException.from_exception(e)
...     print(*tb.format())
...
Traceback (most recent call last):
   File "<stdin>", line 2, in <module>
 some.module.exception_injector.<locals>.product_exception: What's going on?

正在打印的是对象 __module__ 属性值 *,附加了 object.__qualname__ attribute(带有点分隔符),而不是 __module__加上__name__:

>>> namespace["TestError"].__qualname__
'exception_injector.<locals>.product_exception'

限定名称包括 full scope where the class was created(此处为函数名称,但也可以包括 class 名称)。

如果您的目标是向模块的全局命名空间添加例外,您可以将其设置为与 name:

相同的值
>>> namespace["TestError"].__qualname__ = "TestError"
>>> try:
...     raise namespace["TestError"]("What's going on?")
... except Exception as e:
...     tb = traceback.TracebackException.from_exception(e)
...     print(*tb.format())
...
Traceback (most recent call last):
   File "<stdin>", line 2, in <module>
 some.module.TestError: What's going on?

或在您的代码上下文中:

def exception_injector(name, parent, module_dict):
    class product_exception(parent, details=args):
        pass

    product_exception.__name__ = name
    product_exception.__qualname__ = name
    product_exception.__module__ = module_dict["__name__"]
    module_dict[name] = product_exception

* 如果模块名称是 __main__builtins.

,则回溯模块将忽略异常模块