The wxPython Google Group was discussing different methods of catching exceptions in wxPython the other day. If you use wxPython a lot, you will soon realize that some exceptions are difficult to catch. The wxPython Wiki explains why. Anyway, the fellows on the list were recommending the use of sys.excepthook. So I took one of the methods they mentioned and created a little example:
import sys import traceback import wx ######################################################################## 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()
In this example, we create a panel with a button that will deliberately cause an exception to be raised. We catch the exception by redirecting sys.excepthook to our MyExceptionHook function. This function will format the traceback of the exception, format it to make it readable and then display a dialog with the exception information. Robin Dunn, creator of wxPython, thought it would be good if someone came up with a decorator that we could use to catch exception with that could then be added as an example to the wiki page.
My first idea for a decorator was the following:
import logging import wx ######################################################################## 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()
In this code, we create a class that creates a logging instance. Then we override the __call__ method to wrap a method call in an exception handler so we can catch exceptions. Basically what we’re doing here is creating a class decorator. Next we decorate an event handler with our exception logging class. This wasn’t exactly what Mr. Dunn wanted, as the decorator needed to be able to wrap other functions too. So I edited it a bit and came up with the following minor adjustment:
import logging import wx class ExceptionLogging(object): def __init__(self, fn, *args, **kwargs): 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, *args, **kwargs): try: self.fn(self, *args, **kwargs) 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()
This time the __call__ method can accept any number of arguments or keyword arguments, which gives it a bit more flexibility. This still wasn’t what Robin Dunn wanted, so he wrote up the following example:
from __future__ import print_function import logging import wx print(wx.version()) def exceptionLogger(func, loggerName=''): """ A simple decorator that will catch and log any exceptions that may occur to the root logger. """ assert callable(func) mylogger = logging.getLogger(loggerName) # wrap a new function around the callable def logger_func(*args, **kw): try: if not kw: return func(*args) return func(*args, **kw) except Exception: mylogger.exception('Exception in %s:', func.__name__) logger_func.__name__ = func.__name__ logger_func.__doc__ = func.__doc__ if hasattr(func, '__dict__'): logger_func.__dict__.update(func.__dict__) return logger_func def exceptionLog2Logger(loggerName): """ A decorator that will catch and log any exceptions that may occur to the named logger. """ import functools return functools.partial(exceptionLogger, loggerName=loggerName) ######################################################################## 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) #---------------------------------------------------------------------- @exceptionLog2Logger('testLogger') def onExcept(self, event): """ Raise an error """ print(self, event) print(isinstance(self, wx.Panel)) #trigger an exception 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__": # set up the default logger log = logging.getLogger('testLogger') 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) log.addHandler(log_fh) app = wx.App(False) frame = Frame() app.MainLoop()
This shows a couple of different decorator examples. This example demonstrates the more traditional methodology of decorator construction. It has a bit more metaprogramming in it though. The first example checks to make sure what is passed to it is actually callable. Then it creates a logger and wraps the callable with an exception handler. Before it returns the wrapped function, the wrapped function is modified so that it has the same name and docstring as the original function passed to it. I believe you could drop that and use functools.wraps instead, but being explicit is probably better in a tutorial.
Wrapping Up
Now you know how you catch exceptions in a couple of different ways. Hopefully you will find this helpful in your own application design. Have fun!
Pingback: Python Partials - The Mouse Vs. The Python