Как остановить Python от распечатки игнорируемых исключений из других библиотек? - PullRequest
0 голосов
/ 15 января 2020

У меня возникла небольшая проблема с тем, как Python3 обрабатывает указанное исключение c, которое следует игнорировать, но оно все равно распечатывается в конце выполнения вместе с Traceback.

В частности, я использую библиотеку graph-tool для запуска интерактивного окна, например так:

graph_tool.draw.interactive_window(self.graphtool_graph, vertex_text=self.v_label, vertex_font_size=6,geometry=(1920, 1080))

И моя проблема возникает, если я закрываю открытое окно до он заканчивает рисование и выкладывает сам график. Затем, когда выполнение моего кода закончено, я получаю эту распечатку:

Exception ignored in: <bound method GraphWindow.__del__ of <gtk_draw.GraphWindow object at 0x7f221ccf3750 (graph_tool+draw+gtk_draw+GraphWindow at 0x4c662a0)>>
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/graph_tool/draw/gtk_draw.py", line 1183, in __del__
  File "/usr/lib/python3/dist-packages/graph_tool/draw/gtk_draw.py", line 375, in cleanup
  File "/usr/lib/python3/dist-packages/gi/overrides/__init__.py", line 68, in __get__
TypeError: 'NoneType' object is not callable

Что, очевидно, не очень приятно видеть, особенно если это конечный пользователь, взаимодействующий с моим проектом.

Я попытался заключить вызов в draw.interactive_window в оператор try-catch, например, так:

        try:
            graph_tool.draw.interactive_window(self.graphtool_graph, vertex_text=self.v_label, vertex_font_size=6,
                                               geometry=(1920, 1080))
        except TypeError:
            # Interactive Window closed before it finished drawing the graph. It would throw an exception that is
            # quite ugly to see. Let's ignore it.
            return

Но я столкнулся с той же проблемой. Я даже пытался не указывать исключение TypeError и использовать одеяло except, но безрезультатно.

Кто-нибудь знает способ прекратить Python распечатывать это исключение?

PS: Я обнаружил эту проблему на трекере ошибок Python, который кажется связанным. В прилагаемом обсуждении это оформлено как функция ™, а не как ошибка, но мне все равно хотелось бы понять, возможно ли каким-либо образом остановить печать этого исключения, особенно когда я явно пытаюсь его перехватить и проигнорировать.

1 Ответ

1 голос
/ 15 января 2020

Ваша проблема связана с исключениями, возникающими при вызове метода __del__. Как указано здесь ,

Из-за сомнительных обстоятельств, при которых вызываются методы del (), исключения, возникающие во время их выполнения, игнорируются, и Вместо этого в sys.stderr выводится предупреждение

Вот почему ваш блок исключений, кстати, не работает ...

Вы можете проверить это с помощью mcve:

# warntest.py

class Foo(object):
    def __del__(self):
        raise ValueError("YADDA")

f = Foo()

затем

 $ python3 warntest.py 
Exception ignored in: <bound method Foo.__del__ of <__main__.Foo object at 0x7efcb61dc898>>
Traceback (most recent call last):
  File "warntest.py", line 3, in __del__
ValueError: YADDA

Несмотря на то, что может предложить «напечатанное предупреждение» и к моему большому разочарованию, просто запрос Python к предупреждений о тишине ничего не меняет здесь - python3 -Wignore warntest.py ведет себя так же, и ручная настройка фильтра «игнорировать» в скрипте не делает больше.

Я боюсь, здесь нет простого, но чистого решения. Это оставляет вам три возможных варианта:

1 / исправить проблему у источника. graph-tool - это OSS, вы можете внести свой вклад, улучшив эти __del__ методы, чтобы они не вызывали никаких исключений.

2 / используйте хак. Можно обернуть этот вызов менеджером contextlib.redirect_stderr - я пытался, он работает, как и ожидалось, но он не позволит абсолютно ничего достичь stderr во время этого вызова.

3 / жить с ним ...

РЕДАКТИРОВАТЬ

Я попытался найти источник GT и не могу найти, где это исключение

Он написан в виде обычного текста в отосланном вами трекбеке ... Собственно, само исключение возникает в gi/overrides/__init__.py, но не в этом суть - вы хотите отредактировать GraphWindow.__del__GraphWidget.__del__ слишком FWIW), чтобы обернуть вызовы self.graph.cleanup() (self.cleanup() в GraphWidget в самом грубом из возможных блоков try / кроме. Следующий MCVE воспроизводит проблему:

class Sub(object):
    def cleanup(self):
        raise ValueError("YADDA")

    def __del__(self):
        self.cleanup()

class Main(object):
    def __init__(self):
        self.sub = Sub()

    def __del__(self):
        self.sub.cleanup()

Main()

И исправить это довольно просто:

class Sub(object):
    def cleanup(self):
        raise ValueError("YADDA")

    def __del__(self):
        # yes, a bare except clause and a pass...
        # this is exactly what you're NOT supposed to do,
        # never ever, because it's BAD... but here it's
        # ok - provided you double-checked what the code
        # in the `try` block really do, of course.
        try:
            self.cleanup()
        except:
            pass

class Main(object):
    def __init__(self):
        self.sub = Sub()

    def __del__(self):
        try:
            self.sub.cleanup()
        except:
             pass

Важное примечание : этот тип обработчика исключений (за исключением простого предложения, за которым следует pass) равен точно что один должен никогда, никогда делать. Это самое ужасное исключение при обработке antipattern. Но __del__ действительно особый случай, и в текущем случае метод cleanup() только пытается отменить регистрацию функции обратного вызова так что это действительно безвредно.

но даже при перенаправлении вывод в файл

Обратите внимание, что вы хотите перенаправить stderr, а не stdout. У меня работает следующий фрагмент:

import contextlib

class Foo(object):
    def __del__(self):
        raise ValueError("YADDA")

def test():    
    Foo()

with contextlib.redirect_stderr(io.StringIO()):
    test()
...