Welcome, guest | Sign In | My Account | Store | Cart

Lets say you have a function in C or C++ that takes a function callback as an argument. You want to call this function by passing a Python function as the callback. This recipe shows the basics by calling the standard C library function qsort, and passing a python function as the compare function.

Python, 91 lines
 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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "python.h"

extern void qsort(void *, size_t, size_t, int (*)(const void *, const void *));

static PyObject *py_compare_func = NULL;

static int
stub_compare_func(const PyObject **a, const PyObject **b)
{
    int retvalue = 0;

    // Build up the argument list... 
    PyObject *arglist = Py_BuildValue("(OO)", *a, *b);

    // ...for calling the Python compare function.
    PyObject *result = PyEval_CallObject(py_compare_func,arglist);

    if (result && PyInt_Check(result)) {
        retvalue = PyInt_AsLong(result);
    }

    Py_XDECREF(result);
    Py_DECREF(arglist);
    
    return retvalue;
}


static PyObject *pyqsort(PyObject *obj, PyObject *args)
{
    PyObject *pycompobj;
    PyObject *list;
    if (!PyArg_ParseTuple(args, "OO", &list, &pycompobj)) 
        return NULL;
    
    // make sure second argument is a function
    if (!PyCallable_Check(pycompobj)) {
        PyErr_SetString(PyExc_TypeError, "Need a callable object!");
    }
    else {
        // save the compare func. This obviously won't work for multi-threaded
        // programs.
        py_compare_func = pycompobj;
        if (PyList_Check(list)) {
            int size = PyList_Size(list);
            int i;
            
            // make an array of (PyObject *), because qsort does not know about
            // the PyList object
            PyObject **v = (PyObject **) malloc( sizeof(PyObject *) * size );
            for (i=0; i<size; ++i) {
                v[i] = PyList_GetItem(list, i);
                // increment the reference count, because setting the list items below
                // will decrement the ref count
                Py_INCREF(v[i]);
            }
            qsort(v, size, sizeof(PyObject*), stub_compare_func);
            for (i=0; i<size; ++i) {
                PyList_SetItem(list, i, v[i]);
                // need not do Py_DECREF - see above
            }
            free(v);
        }
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef qsortMethods[] = {
    { "qsort", pyqsort, METH_VARARGS },
    { NULL, NULL }
};

__declspec(dllexport) void initqsort(void) {
    PyObject *m;
    m = Py_InitModule("qsort", qsortMethods);
}


In Python
ActivePython 2.1, build 210 ActiveState)
based on Python 2.1 (#15, Apr 23 2001, 18:00:35) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import qsort
>>> a = [9, 3, 5, 4, 1]
>>> def revcmp(a, b): return cmp(b, a)
...
>>> qsort.qsort(a, revcmp)
>>> a
[9, 5, 4, 3, 1]
>>>

When extending Python, one may come across functions that take a function callback. It makes sense to pass Python function as the callback function. The trick is to have a C function callback call the Python function by suitably marshalling the arguments. This is done by the stub_compare_func above. Py_BuildValue is used to pass the two Python objects back to the Python function.

In the case of qsort, there is no userdata that can be passed, as is usually the convention. This means that we have to store the Python function in a static variable, and use it to call in the C callback. This is not an ideal situation (think, multi-threaded). While there is no solution for this particular case (as far as I know), the usual trick is to pass the Python function as userdata to the function callback.

This version of qsort, copies the pointers to the PyObjects to a separate array that is sorted using quicksort. The pointers are then set back in the original list. The refcount of the items in the list that are being replaced are decreased behind the scenes; however, this is ok, because we have increased them beforehand. Consequently we do not need to do a Py_DECREF after setting the item in the list.

4 comments

Hamish Lawson 22 years, 10 months ago  # | flag

Sorted list isn't in reverse order as expected. The output doesn't show the sorted list in the reverse order that would be expected for the given comparision function. I haven't looked through the C code to see if there is actually a coding error there, or whether a slip was made merely in capturing the program's output.

pink chry (author) 22 years, 10 months ago  # | flag

Typo while cut and pasting - apologies. It works as expected when it is run. I made a mistake while pasting the code in. Thanks for pointing it out.

allefant 18 years, 5 months ago  # | flag

maybe could use thread local storage. At least with gcc, you can use the __thread modifier to store the py_compare_func in TLS. Simply do this:

__thread static PyObject *py_compare_func = NULL;

And now each thread should get its own version of the variable, solving the multi-threaded issue.

Laurent THIOUDELLET 18 years ago  # | flag

callable check typo. It looks like after PyCallable_Check is called, a 'return NULL;' is necessary after the PyErr_SetString() line so as the TypeError exception is actually actived.

Created by pink chry on Thu, 14 Jun 2001 (PSF)
Python recipes (4591)
pink chry's recipes (1)
Python Cookbook Edition 1 (103)

Required Modules

  • (none specified)

Other Information and Tasks