The C++ and Python Sandwich

One of the often asked questions for wxPython has to do with why you can't use a global exception handler wrapped around the call to the app object's MainLoop method. To understand the answer to this question you need to keep in mind that wxWidgets and the wxPython wrappers are C++ code and whenever an event handler or other callback is called you end up with another layer of C++ and Python code on the stack. I usually refer to this as the C++/Python sandwich. For example:

  1. You call MainLoop: this is a Python to C++ transition.

  2. The user clicks a button and your EVT_BUTTON handler is called, which transitions back to Python
  3. The handler calls self.SetSize, now we are back in C++

  4. You have a EVT_SIZE handler that is called, which puts us back in Python
  5. The handler calls Layout(), back in C++
  6. The Layout causes some other panel to get resized, and it has it's own EVT_SIZE handler, now we're back in Python again.
  7. That EVT_SIZE handler calls Update to force an immediate repaint of the panel, putting us back in C++
  8. The handler for the EVT_PAINT is called, which puts control on the Python side once more.

So you can see from this example how easy it is for the C++ and Python layers of this sandwich to be built up to Dagwood proportions...

http://en.wikipedia.org/wiki/Dagwood_sandwich

Now for the explanation: In the past when an exception happens in a Python layer of the sandwich there was not any safe way to propagate that exception across the C++ layer to the next Python layer up the stack, so instead when control returns from Python to a C++ layer it checks if there was an exception and simply calls PyErr_Print() if there was. This prints the traceback to sys.stderr, clears the error, and then continues on normally, essentially stopping the propagation of the exception at that layer of the sandwich.

Another way to think about this is to imagine that every call to an event handler or other callback is wrapped in a try/except that simply prints the traceback. So if you want to catch exceptions yourself before the default behavior happens then you need to do it within the same Python layer where the exception happens, before the event handler or callback returns to the C++ layer. It is possible that this can be changed now since wxWidgets has taken more care to be "exception safe" and I think I know how to do it, it's just a matter of finding some time.

Comments...

To make it easier to catch and log exceptions in the layer where they occur I have been using the following class to wrap my event handlers. Of course you can modify the __call__ method to log the event in any way you want (you can also use it to log all calls to the event handler).

class ExceptionLogging:
     def __init__(self, fn):
         self.fn = fn

     def __call__(self,evt):
         try:
             self.fn(evt)
         except Exception, excp:
             log.exception("Exception")

I use it like this:

        self.Bind(gridlib.EVT_GRID_CELL_LEFT_CLICK, ExceptionLogging(self.OnCellLeftClick))

Or, to use it in all Binds in a class, override the class's Bind function:

    def Bind(self, event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY, logCalls=True):
        # Wrap the handler so we can control exception logging
        super(TheClass,self).Bind(event, ExceptionLogging(handler), source, id, id2)

-- TimMorley

C++ & Python Sandwich (last edited 2015-01-15 08:34:48 by RobinDunn)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.