Contents
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:
Or:
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
- SWIG %exception handler
wxWidgets main loop (wxApp::OnExceptionInMainLoop())
Performance Enhancement
- Call Python functions that have no arguments with a shared empty tuple.
TODO: array interface
TODO: inquire about additional areas for performance enhancement
Testing
TODO: exceptions demo.
Unix/Linux wxGTK
- Tested modal dialogs and nested event loops.
- Most event handlers checked.
wxMSW
TODO
MAC
TODO
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.