2

I'm having a bit of a problem with how Python3 is handling a specific Exception, which should be ignored, but that gets printed out anyway at the end of execution along with a Traceback.

Specifically, I use the graph-tool library to launch an interactive window, like so:

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

And my problem arises if I close the window that is opened before it finishes drawing and laying out the graph itself. Then, when execution of my code is finished, I get this printout:

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

Which is obviously not very nice to see, especially if it's an end user interacting with my project.

I tried to enclose the call to draw.interactive_window in a try-catch statement, like so:

        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

But I end up with the same problem. I even tried not specifying the TypeError exception and use a blanket except, but to no avail.

Does anyone know of a way to stop Python from printing out this exception?

P.S.: I found this issue on the Python bug tracker, which seems to be related. In the attached discussion this is framed as a feature™ and not a bug, but I would still like to understand if it is possible in any way to stop this Exception from printing, especially when I am explicitly trying to catch it and ignore it.

Luca Giorgi
  • 910
  • 3
  • 14
  • 28

1 Answers1

1

Your issue comes from exceptions happening in a __del__ method call. As documented here,

Due to the precarious circumstances under which del() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead

Which is why your except block is inoperant by the way...

You can verify this with this mcve:

# warntest.py

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

f = Foo()

then

 $ 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

Despite what the "a warning is printed" might suggest and much to my dismay, just asking Python to silence warnings doesn't change anythin here - python3 -Wignore warntest.py behaves the same, and manually setting the "ignore" filter in the script doesn't do more.

IOW, I'm afraid there's no simple yet clean solution here. This leaves you with three possible options:

1/ fix the issue at the source. graph-tool is OSS, you can contribute by improving those __del__ methods so they don't raise any exception.

2/ use a hack. A possible one is to wrap this call with a contextlib.redirect_stderr manager - I tried, it works as expected, but it will prevent absolutely anything to reach stderr during this call.

3/ live with it...

EDIT

I tried looking into the source of g-t and cannot find where this exception is raised

It's written in plain text in the traceback you posted... Actually, the exception itself is raised in gi/overrides/__init__.py but that's not the point - what you want is to edit GraphWindow.__del__ (and GraphWidget.__del__ too FWIW) to wrap the self.graph.cleanup() ( self.cleanup() in GraphWidget calls in the crudest possible try/except block. The following MCVE reproduces the issue:

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()

And the fix is quite simply:

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

Important note: this kind of exception handler (a bare except clause followed by a pass) is exactly what one should never, ever do. It's the most horrible exception handling antipattern. But __del__ is really a special case, and in the current case the cleanup() method only tries to unregister a callback function so it's really harmless.

but even when redirecting the output to a file

Note that you want to redirect stderr, not stdout. The following snippet works for me:

import contextlib

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

def test():    
    Foo()

with contextlib.redirect_stderr(io.StringIO()):
    test()
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thanks for you answer! I tried looking into the source of g-t and cannot find where this exception is raised, so for now I would not consider option 1. Option 2 seems to be the most sensible provided that it is redirected somewhere one can access it later to check for actual errors, but even when redirecting the output to a file it still gets printed to console and the file is empty! I'm guessing it doesn't come from this function call? – Luca Giorgi Jan 15 '20 at 21:31