Introduction

Most Python wrappers must at some point manage callbacks to functions written in the scripting language. The Python C API has a number of functions like PyObject_CallObject(), PyObject_CallFunction(), PyObject_CallMethod(), etc, which make it easy for a wrapper programmer to call Python methods or functions. However, errors generated by script callbacks in wrappers are normally handled in a very simple fashion through PyErr_Print(). This behavior violates what is expected normally from a Python program; if a script is written in pure Python, exceptions triggered from within callbacks or member functions are propagated to the caller if they are not caught before.

wxPython deals with callback exceptions in the standard way with PyErr_Print(). My project is to modify the applicable parts of wxPython to propagate Python exceptions back to the Python script, so that the script can catch the exceptions or generate stack traces, benefits a system written in pure Python would enjoy. This project will help to promote consistency between wrapper and Python, and make errors easier for a script programmer track down.

Robin Dunn suggested using C++ exceptions to propagate the Python exceptions up the stack, so that is the method I will use in this project.

In addition, I will be finding areas for performance enhancement, starting by adding support for the numpy array interface where appropriate.

Tasks

Exception Safety

Added wxPyThreadBlocker and wxPyObject classes to help with exception safety. The classes are inlined in include/wx/wxPython/raiihelpers.h

wxPyThreadBlocker

GIL critical sections can be written like so:

   1 void somefunction()
   2 {
   3     wxPyThreadBlocker blocker;
   4     //...Use Python API (actions may throw)...
   5 }

Or:

   1 void somefunction()
   2 {
   3     wxPyThreadBlocker bocker(PTB_INIT_UNLOCK);
   4 
   5     //...Actions that don't require GIL...
   6 
   7     blocker.Block();
   8     //...Use Python API...
   9     blocker.Unblock();
  10 
  11     //ETC...
  12 }

In both cases, the GIL is released automatically when somefunction() terminates if it was held before.

wxPyObject

wxPyObject helps reduce the need for explicit calls to Py_DECREF() by performing the DECREF operation in the destructor.

Here are some examples:

   1 void somefunction()
   2 {
   3     wxPyObject o, o2;
   4 
   5     // Assignment of a plain PyObject*: o takes reference to the list.
   6     o = PyList_New(2);
   7 
   8     // Ok(): check for not NULL
   9     if (!o.Ok()) {
  10         // Handle error
  11     }
  12 
  13     // Assignment by another wxPyObject: o2 adds a new reference to o.  
  14     o2 = o;
  15     // Assignment by another wxPyObject with the same underlying PyObject pointer: noop.
  16     o2 = o;
  17 
  18     // Reasignment: o2 drops reference to o taking the new reference to the PyInt
  19     o2 = PyInt_FromLong(5);
  20 
  21     // Get(): Returns the PyObject pointer.
  22     // Transfer(): Returns PyObject, transfering reference. 
  23     // o2 continues to "borrow" the underlying object, but it wont DECREF it.
  24     PyList_SetItem(o.Get(), 0, o2.Transfer());
  25 
  26     // Clear(): if not borrowed, DECREF underlying object. 
  27     // Set object to NULL (so that Get() == NULL && Ok() == false)
  28     o2.Clear();
  29 
  30     // Ref(): Add reference the PyObject
  31     o2.Ref(Py_None);
  32 
  33     PyList_SetItem(o.Get(), 1, o2.Transfer());
  34 
  35     // Borrow(): Borrow a reference 
  36     o2.Borrow(PyList_GetItem(o.Get(), 0));
  37 
  38     // Termination: DECREF o, and skip o2 (borrowed)
  39     return;
  40 }

Exception Throw Points

Modified the callback API to optionally throw an exception, when the PROPAGATE_EXCEPTIONS option in config.py is set on. If set to propagate, all frames in the path of the unwinding must know how cleanup after themselves. If PROPAGATE_EXCEPTIONS is set off, the exception is printed and execution continues.

wxPyCallback::EventThunker() also throws on callback failure.

Exception Catch Points

Performance Enhancement

Testing

TODO: exceptions demo.

Unix/Linux wxGTK

wxMSW

MAC

End of GSoC Summary

I've completed the tasks listed on my original proposal for the most part. I made additional modifications to improve code clarity dealing with type conversion and argument passing. The callback preprocessor macros were overhauled to use the insertion and extraction operators. Wrapping new callbacks should hopefully be simpler, as in many cases, new wrappers can be expressed using the existing macros and insertion and extraction operators. Additional insertion and extraction operators may be provided at the module level for new types.

Testing is a bit challenging, because the changes effect wxPython broadly. In addition, some of wxWidgets wasn't exception safe, but it wasn't a big hurdle to create patches to fix these problems. I'd like to come up with a demo or unit test to stress wxWidgets and wxPython in a more formal way than my recent ad-hoc testing has. Testing on wxMSW and Mac is still TODO.

I didn't have time to make the performance enhancements during the GSoC period. Adding support for the n-d array interface for image processing, etc, was one of the suggestions (from the looks of it, the scipy array interface may be included in future versions of Python). There may be other areas to speed up.

Overall, I learned quite a bit about the internals of wxPython, some extra details of wxWidgets, and the procedure for suggesting bug fixes/reports to the project. I still have a lot to learn about the actual use of wxPython, but this GSoC project has given me a good vantage point to begin learning.

GSoC2008/RecognizingPythonCallbackExceptions (last edited 2008-08-26 08:50:50 by ip68-7-74-223)

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