更改不可调用模块的回溯

时间:2018-06-28 15:40:45

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

我只是一个包的次要贡献者,人们必须这样做(Foo.Bar.Bar是一类):

>>> from Foo.Bar import Bar
>>> s = Bar('a')

有时候人们会错误地这样做(Foo.Bar是一个模块):

>>> from Foo import Bar   
>>> s = Bar('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'module' object is not callable

这似乎很简单,但是用户仍然无法对其进行调试,我想使其更容易。我无法更改FooBar的名称,但是我想添加更多信息,例如:

TypeError("'module' object is not callable, perhaps you meant to call 'Bar.Bar()'")

我阅读了Callable modules的问答,而且我知道我无法向模块添加__call__方法(并且我不想为此将整个模块包装在一个类中) )。无论如何,我不希望模块可调用,我只希望自定义回溯。是否有适用于Python 3.x和2.7+的干净解决方案?

3 个答案:

答案 0 :(得分:4)

将其添加到Bar.py的顶部:(基于this question

import sys

this_module = sys.modules[__name__]
class MyModule(sys.modules[__name__].__class__):
    def __call__(self, *a, **k):  # module callable
        raise TypeError("'module' object is not callable, perhaps you meant to call 'Bar.Bar()'")
    def __getattribute__(self, name):
        return this_module.__getattribute__(name)

sys.modules[__name__] = MyModule(__name__)


# the rest of file
class Bar:
    pass

注意:已在python3.6和python2.7上进行过测试。

答案 1 :(得分:3)

您想要的是在显示给用户时更改错误消息。一种方法是定义自己的excepthook

您自己的功能可以:

  • 在回溯对象(包含有关TypeError异常以及执行该异常的函数的信息)中搜索调用帧,
  • 在局部变量中搜索Bar对象,
  • 如果对象是模块而不是类或函数,则更改错误消息。

Foo.__init__.py中,您可以安装除钩子

import inspect
import sys


def _install_foo_excepthook():
    _sys_excepthook = sys.excepthook

    def _foo_excepthook(exc_type, exc_value, exc_traceback):
        if exc_type is TypeError:
            # -- find the last frame (source of the exception)
            tb_frame = exc_traceback
            while tb_frame.tb_next is not None:
                tb_frame = tb_frame.tb_next

            # -- search 'Bar' in the local variable
            f_locals = tb_frame.tb_frame.f_locals
            if 'Bar' in f_locals:
                obj = f_locals['Bar']
                if inspect.ismodule(obj):
                    # -- change the error message
                    exc_value.args = ("'module' object is not callable, perhaps you meant to call 'Foo.Bar.Bar()'",)

        _sys_excepthook(exc_type, exc_value, exc_traceback)

    sys.excepthook = _foo_excepthook


_install_foo_excepthook()

当然,您需要执行此算法…

具有以下示例:

# coding: utf-8

from Foo import Bar

s = Bar('a')

您得到:

Traceback (most recent call last):
  File "/path/to/demo_bad.py", line 5, in <module>
    s = Bar('a')
TypeError: 'module' object is not callable, perhaps you meant to call 'Foo.Bar.Bar()'

答案 2 :(得分:1)

有很多方法可以获取不同的错误消息,但是它们都有奇怪的警告和副作用。

  • __class__子类替换模块的types.ModuleType可能是最干净的选择,但仅适用于Python 3.5 +。

    除了3.5+的限制,我想到的这个选项的主要怪异副作用是,该模块将被报告为callable函数可调用的,并且重新加载该模块将再次替换其类,除非您要小心避免这种双重替换。

  • 将模块对象替换为其他对象可在3.5之前的Python版本上使用,但要完全正确将非常棘手。

    子模块,重新加载,全局变量,除自定义错误消息外的任何模块功能...如果您错过实现的某些细微方面,所有这些功能都可能会破坏。同样,该模块将被报告为callable可调用,就像用__class__替换一样。

  • 可以在引发异常后(例如在sys.excepthook中尝试修改异常消息,但是没有一种很好的方法来告知任何特定的TypeError来自尝试将模块作为函数调用。

    您可能最好的办法是,在一个似乎可以调用您的模块的命名空间中,检查带有TypeError消息的'module' object is not callable,例如,如果{{ 1}}名称绑定到框架的本地变量或全局变量中的Bar模块-但这仍然会有很多假阴性和假阳性。另外,Foo.Bar替换与IPython不兼容,并且您使用的任何机制都可能与某些东西冲突。

现在,您所遇到的问题很容易理解和解释。尝试更改错误消息时可能遇到的问题可能很难理解,也很难解释。这可能不是一个值得的权衡。