One of my friends on the wxPython Google Group asked how to catch any exception that happens in wxPython. The problem is complicated somewhat because wxPython is a wrapper on top of a C++ library (wxWidgets). You can read about the issue on the wxPython wiki. Several wxPython users mentioned using Python's sys.excepthook to catch the errors. So I decided to write up an example showing how that worked based on something that Andrea Gavana posted on the aforementioned thread. We will also look at the solution that is in that wiki link.
It ended up being a bit more work than I expected as I ended up needing to import Python's traceback module and I decided I wanted to display the error, so I also created a dialog. Let's take a look at the code:
import sys import traceback import wx import wx.lib.agw.genericmessagedialog as GMD ######################################################################## class Panel(wx.Panel): """""" #---------------------------------------------------------------------- def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) #---------------------------------------------------------------------- def onExcept(self, event): """ Raise an error """ 1/0 ######################################################################## class Frame(wx.Frame): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") sys.excepthook = MyExceptionHook panel = Panel(self) self.Show() ######################################################################## class ExceptionDialog(GMD.GenericMessageDialog): """""" #---------------------------------------------------------------------- def __init__(self, msg): """Constructor""" GMD.GenericMessageDialog.__init__(self, None, msg, "Exception!", wx.OK|wx.ICON_ERROR) #---------------------------------------------------------------------- def MyExceptionHook(etype, value, trace): """ Handler for all unhandled exceptions. :param `etype`: the exception type (`SyntaxError`, `ZeroDivisionError`, etc...); :type `etype`: `Exception` :param string `value`: the exception error message; :param string `trace`: the traceback header, if any (otherwise, it prints the standard Python header: ``Traceback (most recent call last)``. """ frame = wx.GetApp().GetTopWindow() tmp = traceback.format_exception(etype, value, trace) exception = "".join(tmp) dlg = ExceptionDialog(exception) dlg.ShowModal() dlg.Destroy() #---------------------------------------------------------------------- if __name__ == "__main__": app = wx.App(False) frame = Frame() app.MainLoop()
This code is a bit involved so we'll spend a little time on each section. The Panel code has one button on it that will call a method that will cause a ZeroDivisionError. In the Frame class, we set sys.excepthook to a custom function, MyExceptionHook. Let's take a look at that:
#---------------------------------------------------------------------- def MyExceptionHook(etype, value, trace): """ Handler for all unhandled exceptions. :param `etype`: the exception type (`SyntaxError`, `ZeroDivisionError`, etc...); :type `etype`: `Exception` :param string `value`: the exception error message; :param string `trace`: the traceback header, if any (otherwise, it prints the standard Python header: ``Traceback (most recent call last)``. """ frame = wx.GetApp().GetTopWindow() tmp = traceback.format_exception(etype, value, trace) exception = "".join(tmp) dlg = ExceptionDialog(exception) dlg.ShowModal() dlg.Destroy()
This function accepts 3 arguments: etype, value and the traceback. We use the traceback module to put those pieces together to get us a full traceback that we can pass to our message dialog.
Robin Dunn (creator of wxPython) mentioned that there was a solution on the wiki in that same thread above and that he'd like to see it used as a decorator. Here is my implementation:
import logging import wx import wx.lib.agw.genericmessagedialog as GMD ######################################################################## class ExceptionLogging(object): #---------------------------------------------------------------------- def __init__(self, fn): self.fn = fn # create logging instance self.log = logging.getLogger("wxErrors") self.log.setLevel(logging.INFO) # create a logging file handler / formatter log_fh = logging.FileHandler("error.log") formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s") log_fh.setFormatter(formatter) self.log.addHandler(log_fh) #---------------------------------------------------------------------- def __call__(self,evt): try: self.fn(self, evt) except Exception, e: self.log.exception("Exception") ######################################################################## class Panel(wx.Panel): """""" #---------------------------------------------------------------------- def __init__(self, parent): """Constructor""" wx.Panel.__init__(self, parent) btn = wx.Button(self, label="Raise Exception") btn.Bind(wx.EVT_BUTTON, self.onExcept) #---------------------------------------------------------------------- @ExceptionLogging def onExcept(self, event): """ Raise an error """ 1/0 ######################################################################## class Frame(wx.Frame): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" wx.Frame.__init__(self, None, title="Exceptions") panel = Panel(self) self.Show() #---------------------------------------------------------------------- if __name__ == "__main__": app = wx.App(False) frame = Frame() app.MainLoop()
We use the custom exception class to log errors. To apply that class to our event handlers, we decorate them with the class using @classname, which in this case translates into @ExceptionLogging. Thus whenever this event handler is called, it is run through the decorator which wraps the event handler in a try/except and logs all exceptions to disk. I'm not entirely sure if both of the methods mentioned in this article can catch the same errors or not. Feel free to let me know in the comments.
Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary