Programming Python

23.4. Registering Callback Handler Objects

In the examples thus far, C has been running and calling Python code from a standard main program flow of control. That's not always the way programs work, though; in some cases, programs are modeled on an event-driven architecture in which code is executed only in response to some sort of event. The event might be an end user clicking a button in a GUI, the operating system delivering a signal, or simply software running an action associated with an entry in a table.

In any event (pun accidental), program code in such an architecture is typically structured as callback handlerschunks of code dispatched by event-processing logic. It's easy to use embedded Python code to implement callback handlers in such a system; in fact, the event-processing layer can simply use the embedded-call API tools we saw earlier in this chapter to run Python handlers.

The only new trick in this model is how to make the C layer know what code should be run for each event. Handlers must somehow be registered to C to associate them with future events. In general, there is a wide variety of ways to achieve this code/event association; for instance, C programs can:

  • Fetch and call functions by event name from one or more module files

  • Fetch and run code strings associated with event names in a database

  • Extract and run code associated with event tags in HTML or XML[*]

    [*] If C chooses to do so, it might even run embedded Python code that uses Python's standard HTML and XML processing tools to parse out the embedded code associated with an event tag. See the Python library manual for details on these parsers.

  • Run Python code that calls back to C to tell it what should be run

And so on. Really, any place you can associate objects or strings with identifiers is a potential callback registration mechanism. Some of these techniques have advantages all their own. For instance, callbacks fetched from module files support dynamic reloading (reload works on modules but does not update objects held directly). And none of the first three schemes requires users to code special Python programs that do nothing but register handlers to be run later.

It is perhaps more common, though, to register callback handlers with the last approach: letting Python code register handlers with C by calling back to C through extension interfaces. Although this scheme is not without trade-offs, it can provide a natural and direct model in scenarios where callbacks are associated with a large number of objects.

For instance, consider a GUI constructed by building a tree of widget objects in Python scripts. If each widget object in the tree can have an associated event handler, it may be easier to register handlers by simply calling methods of widgets in the tree. Associating handlers with widget objects in a separate structure such as a module file or an HTML file requires extra cross-reference work to keep the handlers in sync with the tree.[*]

[*] If you're looking for a more realistic example of Python callback handlers, see the Tkinter GUI system used extensively in this book. Tkinter uses both extending and embedding. Its extending interface (widget objects) is used to register Python callback handlers, which are later run with embedding interfaces in response to GUI events. You can study Tkinter's implementation in the Python source distribution for more details, though its Tk library interface logic makes it a somewhat challenging read.

The following C and Python files demonstrate the basic coding techniques used to implement explicitly registered callback handlers. The C file in Example 23-9 implements interfaces for registering Python handlers, as well as code to run those handlers in response to events:

Event router

The Route_Event function responds to an event by calling a Python function object previously passed from Python to C.

Callback registration

The Register_Handler function saves a passed-in Python function object pointer in a C global variable. Python calls Register_Handler through a simple cregister C extension module created by this file.

Event trigger

To simulate real-world events, the trigger_Event function can be called from Python through the generated C module to trigger an event.

In other words, this example uses both the embedding and the extending interfaces we've already met to register and invoke Python event handler code.

Example 23-9. PP3E\Integrate\Mixed\Regist\cregister.c

#include <Python.h> #include <stdlib.h> /***********************************************/ /* 1) code to route events to Python object */ /* note that we could run strings here instead */ /***********************************************/ static PyObject *Handler = NULL; /* keep Python object in C */ void Route_Event(char *label, int count) { char *cres; PyObject *args, *pres; /* call Python handler */ args = Py_BuildValue("(si)", label, count); /* make arg-list */ pres = PyEval_CallObject(Handler, args); /* apply: run a call */ Py_DECREF(args); /* add error checks */ if (pres != NULL) { /* use and decref handler result */ PyArg_Parse(pres, "s", &cres); printf("%s\n", cres); Py_DECREF(pres); } } /*****************************************************/ /* 2) python extension module to register handlers */ /* python imports this module to set handler objects */ /*****************************************************/ static PyObject * Register_Handler(PyObject *self, PyObject *args) { /* save Python callable object */ Py_XDECREF(Handler); /* called before? */ PyArg_Parse(args, "O", &Handler); /* one argument? */ Py_XINCREF(Handler); /* add a reference */ Py_INCREF(Py_None); /* return 'None': success */ return Py_None; } static PyObject * Trigger_Event(PyObject *self, PyObject *args) { /* let Python simulate event caught by C */ static count = 0; Route_Event("spam", count++); Py_INCREF(Py_None); return Py_None; } static struct PyMethodDef cregister_methods[] = { {"setHandler", Register_Handler}, /* name, address */ {"triggerEvent", Trigger_Event}, {NULL, NULL} }; void initcregister( ) /* this is called by Python */ { /* on first "import cregister" */ (void) Py_InitModule("cregister", cregister_methods); }

Ultimately, this C file is an extension module for Python, not a standalone C program that embeds Python (though C could just as well be on top). To compile it into a dynamically loaded module file, run the makefile in Example 23-10 on Cygwin (and use something similar on other platforms). As we learned in the last chapter, the resulting cregister.dll file will be loaded when first imported by a Python script if it is placed in a directory on Python's module search path (e.g., . or PYTHONPATH settings).

Example 23-10. PP3E\Integrate\Mixed\Regist\makefile.regist

###################################################################### # Cygwin makefile that builds cregister.dll. a dynamically loaded # C extension module (shareable), which is imported by register.py ###################################################################### PYLIB = /usr/bin PYINC = /usr/include/python2.4 CMODS = cregister.dll all: $(CMODS) cregister.dll: cregister.c gcc cregister.c -g -I$(PYINC) -shared -L$(PYLIB) -lpython2.4 -o $@ clean: rm -f *.pyc $(CMODS)

Now that we have a C extension module set to register and dispatch Python handlers, all we need are some Python handlers. The Python module shown in Example 23-11 defines two callback handler functions and imports the C extension module to register handlers and trigger events.

Example 23-11. PP3E\Integrate\Mixed\Regist\register.py

####################################x################### # register for and handle event callbacks from C; # compile C code, and run with 'python register.py' ####################################################### # # C calls these Python functions; # handle an event, return a result # def callback1(label, count): return 'callback1 => %s number %i' % (label, count) def callback2(label, count): return 'callback2 => ' + label * count # # Python calls a C extension module # to register handlers, trigger events # import cregister print '\nTest1:' cregister.setHandler(callback1) for i in range(3): cregister.triggerEvent( ) # simulate events caught by C layer print '\nTest2:' cregister.setHandler(callback2) for i in range(3): cregister.triggerEvent( ) # routes these events to callback2

That's itthe Python/C callback integration is set to go. To kick off the system, run the Python script; it registers one handler function, forces three events to be triggered, and then changes the event handler and does it again:

.../PP3E/Integration/Mixed/Regist$ make -f makefile.regist gcc cregister.c -g -I/usr/include/python2.4 -shared -L/usr/bin -lpython2.4 -o cregister.dll .../PP3E/Integration/Mixed/Regist$ python register.py Test1: callback1 => spam number 0 callback1 => spam number 1 callback1 => spam number 2 Test2: callback2 => spamspamspam callback2 => spamspamspamspam callback2 => spamspamspamspamspam

This output is printed by the C event router function, but its content is the return values of the handler functions in the Python module. Actually, something pretty wild is going on under the hood. When Python forces an event to trigger, control flows between languages like this:

  1. From Python to the C event router function

  2. From the C event router function to the Python handler function

  3. Back to the C event router function (where the output is printed)

  4. And finally back to the Python script

That is, we jump from Python to C to Python and back again. Along the way, control passes through both extending and embedding interfaces. When the Python callback handler is running, two Python levels are active, and one C level in the middle. Luckily, this just works; Python's API is reentrant, so you don't need to be concerned about having multiple Python interpreter levels active at the same time. Each level runs different code and operates independently.

Категории