A Python 2.4 (or later) decorator can be used to watch for unhandled exceptions, even in hybrid environments like wxPython. You place the decorator around methods that will be invoked by the wxWidgets code. The only big trick is a dance that may be necessary to provide access to values only available after the wxPthon app is started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
import wx, os, sys errorframe = None def watcherrors(function): '''function decorator to display Exception information.''' def substitute(*args, **kwargs): try: return function(*args, **kwargs) except Exception: error_type, error, traceback = sys.exc_info() mb = wx.MessageDialog(errorframe, '%s\n\nClick OK to see traceback' % error, 'Error in Run', wx.OK | wx.CANCEL | wx.ICON_ERROR) if wx.ID_OK == mb.ShowModal(): mb.Destroy() from traceback import extract_tb trace = ['%s line %s in %s:\n\t%s' % ( os.path.split(filename), line, fun, text) for filename, line, fun, text in extract_tb(traceback)] mb = wx.MessageDialog(errorframe, '\n'.join(['%s\n' % error] + trace), 'Run Error w/ Traceback', wx.OK | wx.ICON_ERROR) result = mb.ShowModal() mb.Destroy() raise # Optional-- you may want to squech some errors here try: substitute.__doc__ = function.__doc__ except AttributeError: pass return substitute # You can use it as follows to wrap functions and methods: ''' class .... @watcherrors def something(somearg) if somearg is not None: raise ValueError(somearg) ... ''' # Alternative: (watcherrors is defined almost as above) def augmentedwatch(holder, framefieldname): def watcherrors(function: '...' # -- same as above def substitute(*args, **kwargs): try: return function(*args, **kwargs) except Exception: error_type, error, traceback = sys.exc_info() ### new code: errorframe = getattr(holder, framefieldname, None) '...' # -- same as above return substitute return watcherrors # You use this as follows to wrap functions and methods, where # holder is an expression at "first import" time that will, at # the time of one of the uncaught exceptions, have an attribute # named 'frame': ''' class .... @augmentedwatch(holder, 'frame') def something(somearg) if somearg is not None: raise ValueError(somearg) ... '''
Wrapping the code this way allows you to say, watcher-by-watcher, where to get the parent for the message box. You will know which of your methods will need to be wrapped; only those tied to wx events. "watcherrors" gets any information it needs from globals in the module defining watcherrors. Often, that is not such a great idea.
In case the "watcherrors" technique doesn't work for your code structure, you may need another layer of function-producing function (augmentedwatch) which knows where to get the data from. The 'holder' parameter needs to exist when the methods (or functions) are being defined. One possible holder is a class name already defined above in the same source. Another possible holder is a module name defined in an import statement, and a final possibility is an expression like "__import('__main__')".
Again, for augmentedwatch, the 'holder' expression is evaluated when defining the function or method. However, holder need not have the 'framefieldname' field appropriately defined until the moment the exception is caught.