Изменение отслеживания неотзываемого модуля - PullRequest
0 голосов
/ 28 июня 2018

Я незначительный участник пакета, в котором люди должны это делать (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

Это может показаться простым, но пользователи все еще не могут отладить его, я хотел бы сделать это проще. Я не могу изменить имена Foo или Bar, но я бы хотел добавить более информативную трассировку, например:

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

Я прочитал Вызываемые модули Вопросы и ответы, и я знаю, что не могу добавить __call__ метод к модулю (и я не хочу заключать весь модуль в класс только для этот). Как бы то ни было, я не хочу, чтобы модуль вызывался, я просто хочу пользовательскую трассировку. Есть ли чистое решение для Python 3.x и 2.7 +?

Ответы [ 3 ]

0 голосов
/ 17 июля 2018

Добавьте это к началу Bar.py: (на основе этот вопрос )

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.

0 голосов
/ 19 июля 2018

Существует множество способов получить другое сообщение об ошибке, но все они имеют странные предостережения и побочные эффекты.

  • Замена __class__ модуля на подкласс types.ModuleType, вероятно, является самым чистым вариантом, но он работает только на Python 3.5+.

    Помимо ограничения 3.5+, основные странные побочные эффекты, о которых я подумал для этой опции, заключаются в том, что модуль будет вызывать функцию callable, и перезагрузка модуля снова заменит его класс, если вы не ' Будьте осторожны, чтобы избежать такой двойной замены.

  • Замена объекта модуля другим объектом работает в версиях Python до 3.5, но очень сложно получить полностью правильную информацию.

    Подмодули, перезагрузка, глобальные переменные, любые функциональные возможности модуля, кроме настраиваемого сообщения об ошибке ... все они могут сломаться, если вы пропустите какой-то тонкий аспект реализации. Кроме того, модуль будет вызываться callable, как и при замене __class__.

  • Попытка изменить сообщение об исключении после возникновения исключения, например, в sys.excepthook, возможна, но нет хорошего способа сказать, что какой-то конкретный TypeError произошел от попытки вызвать ваш модуль как функция.

    Вероятно, лучшее, что вы могли бы сделать, это проверить TypeError с сообщением 'module' object is not callable в пространстве имен, где кажется вероятным, что ваш модуль был бы вызван - например, если имя Bar связано к модулю Foo.Bar в локальных или глобальных кадрах фрейма - но все равно будет много ложных негативов и ложных срабатываний. Кроме того, замена sys.excepthook несовместима с IPython, и любой используемый вами механизм, вероятно, будет конфликтовать с чем-то.

Прямо сейчас проблемы, которые у вас есть, легко понять и легко объяснить. Проблемы, которые могут возникнуть при любой попытке изменить сообщение об ошибке, вероятно, будут гораздо сложнее понять и труднее объяснить. Это, вероятно, не стоящий компромисс.

0 голосов
/ 12 июля 2018

То, что вы хотите, это изменить сообщение об ошибке, когда отображается для пользователя. Один из способов сделать это - определить свой собственный , за исключением .

Ваша собственная функция может:

  • поиск вызывающего кадра в объекте трассировки (который содержит информацию об исключении TypeError и функции, которая делает это),
  • поиск объекта Bar в локальных переменных,
  • изменить сообщение об ошибке, если объект является модулем, а не классом или функцией.

В Foo.__init__.py вы можете установить свой exchook

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()'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...