Extending Python 

by Greg Smith      



Listing One



/* ---------- stats.c --------------

 * simple example of a C module

 */

#include <math.h>

#include "stats.h"

/* given a set of pointers to 'n' floats (>=1), find the mean and variance. 

 * The variance is returned directly; the mean via a pointer.

 */

double

find_variance( float const *data, int npts, double *mean_p)

{

    int i;

   double sum = 0;

    double sumsq = 0;

    double mean;

    /*

     * there are more numerically stable ways to do this, but

     * that's a different article...

     */

    for( i = 0; i < npts; ++ i ){

        double x= data[i];

        sum += x;

        sumsq += x*x;

    }

    mean = sum/npts;

    *mean_p = mean;

    return ( sumsq - mean*sum )/npts ;

}





Listing Two



/* ---------- statsmodule.c -------------- */

/* python extension based on stats.c       */



#include <Python.h>

#include "stats.h"



static char stats_find_variance_doc[] = 

"Given a sequence of floats, find the standard deviation and mean";



static PyObject *

stats_find_variance(PyObject *self, PyObject *args)

{

    double mean, var;

    PyObject * flt_list;

    PyObject * lst_element;

    int n,i;

    float *tmp_arr;

    /* must be a single parameter which is a sequence of floats. */

    if( PySequence_Size(args)!=1 ){

        flt_list = 0;

    }else{

        flt_list = PySequence_GetItem(args,0);

    }

    /* at this point either flt_list is NULL, or

     * it's the param and we own a reference to it

     */

    if( flt_list ==0  || !PySequence_Check(flt_list)){

        PyErr_SetString( PyExc_TypeError, 

                "find_variance accepts a list of floats");

        Py_XDECREF(flt_list);

        return 0;

   }

    /* we know it's a sequence. Get its length, and allocate an

     * array of floats. Length of 0 is an error.

     */

    n = PySequence_Size( flt_list );

    if( n < 1 ){

        PyErr_SetString( PyExc_ValueError,"find_variance: empty list");

        Py_XDECREF(flt_list);

        return 0;

    }

    tmp_arr= PyMem_Malloc( n * sizeof(float));



    if( !tmp_arr ){

        PyErr_SetString( PyExc_MemoryError, "out of memory");

        Py_DECREF(flt_list);

        return 0;

    }

    /* now fill in all the floats. If any operation fails, we have to free 

     * everything. The code is structured so that if something returns NULL, 

     * it will drop down to the single error test. We don't need to

     * set the exception, since the failed operation will do it.

     */

    for( i = 0; i < n ; i++ ){

        lst_element = PySequence_GetItem( flt_list, i );



        if( lst_element != 0 && !PyFloat_Check(lst_element)){

            /* succeeded, but not a float - convert it */

            PyObject *new_obj= PyNumber_Float( lst_element );

            Py_DECREF(lst_element); 

            lst_element = new_obj;  /* may be 0 */

        }

        if( lst_element != 0 ){

            tmp_arr[i] = (float)PyFloat_AsDouble(lst_element);

            Py_DECREF(lst_element);

        }else{

            Py_DECREF(flt_list);

            PyMem_Free(tmp_arr);

            return 0;

        }

    }

    Py_DECREF(flt_list);    /* don't need that any more */

    /* call the actual C routine with all the floats */

    var = find_variance( tmp_arr, n , & mean );

    /* free our tmp array and return results */

    PyMem_Free(tmp_arr);

    return Py_BuildValue("dd", var, mean);

}

/* table of module methods. If the module has multiple

 * Python-callable functions, they are all listed here.

 */

static PyMethodDef StatsMethods[] = {

{"find_variance",  stats_find_variance,

    METH_VARARGS,  stats_find_variance_doc},

    {NULL, NULL, 0, NULL}        /* Sentinel */

};

/* The module initialization function */

PyMODINIT_FUNC

initstats(void)

{

    Py_InitModule("stats", StatsMethods);

}





Listing Three



/* ---------- statsimodule.c ----------------- */



#include <Python.h>

#include "stats.h"

/*

 * This is an extension implementing the 'stats' module which is intended to 

 * be wrapped by a python module; it provides a simple internal interface.

 * the 'find_variance' entry point accepts a single parameter, which 

 * is expected to be a read-only buffer of floats (at least one)

 */

static PyObject *

statsi_find_variance(PyObject *self, PyObject *args)

{

    double mean, var;

    char *data_ptr;

    int nbytes,n;



    /* expect a single parameter, which is a string or buffer, containing 

     * value in native order. The API call below gets us the address and len

     * of the string, or fails.

     */

    if (!PyArg_ParseTuple(args, "s#", &data_ptr, &nbytes))

        return NULL;

    /* check the len - it must be a multiple of sizeof(float) */

    n = nbytes/sizeof(float);



   if( n < 0 || n*sizeof(float) != nbytes ){

        PyErr_SetString( PyExc_ValueError,

                 "find_variance: bad lengtht");

        return 0;

    }

    /* call the actual C routine with all the floats. Note: there is a risk 

     * here that the data will not be aligned on a suitable boundary for 

     * floats. This is not an issue on X86 hardware; also, the python wrapper

     * can guarantee this, if needed, by using array.array type 

     * instead of a string.

     */

    var = find_variance( (float const*) data_ptr, n , & mean );

    /* return results */

    return Py_BuildValue("dd", var, mean);

}

/* table of module methods. If the module has multiple

 * Python-callable functions, they are all listed here.

 */

static PyMethodDef StatsiMethods[] = {

{"find_variance",  statsi_find_variance,

    METH_VARARGS,  ""},

    {NULL, NULL, 0, NULL}        /* Sentinel */

};

/* The module initialization function */

PyMODINIT_FUNC

initstatsi(void)

{

    Py_InitModule("statsi", StatsiMethods);

}









4



