I l@ve RuBoard Previous Section Next Section

19.6 A C Extension Module String Stack

Let's kick it up another notch -- the following C extension module implements a stack of strings for use in Python scripts. Example 19-14 demonstrates additional API calls, but also serves as a basis of comparison. It is roughly equivalent to the Python stack module we met earlier in Chapter 14 but it stacks only strings (not arbitrary objects), has limited string storage and stack lengths, and is written in C.

Alas, the last point makes for a complicated program listing -- C code is never quite as nice to look at as equivalent Python code. C must declare variables, manage memory, implement data structures, and include lots of extra syntax. Unless you're a big fan of C, you should focus on the Python interface code in this file, not the internals of its functions.

Example 19-14. PP2E\Integrate\Extend\Stacks\stackmod.c
/*****************************************************
 * stackmod.c: a shared stack of character-strings;
 * a C extension module for use in Python programs;
 * linked into python libraries or loaded on import;
 *****************************************************/

#include "Python.h"             /* Python header files */
#include <stdio.h>              /* C header files */
#include <string.h>

static PyObject *ErrorObject;   /* locally-raised exception */

#define onError(message) \
       { PyErr_SetString(ErrorObject, message); return NULL; }

/******************************************************************************
* LOCAL LOGIC/DATA (THE STACK)
******************************************************************************/

#define MAXCHARS 2048
#define MAXSTACK MAXCHARS

static int  top = 0;                 /* index into 'stack' */
static int  len = 0;                 /* size of 'strings' */
static char *stack[MAXSTACK];        /* pointers into 'strings' */
static char strings[MAXCHARS];       /* string-storage area */

/******************************************************************************
* EXPORTED MODULE METHODS/FUNCTIONS
******************************************************************************/

static PyObject *
stack_push(PyObject *self, PyObject *args)       /* args: (string) */
{
    char *pstr;
    if (!PyArg_ParseTuple(args, "s", &pstr))     /* convert args: Python->C */
        return NULL;                             /* NULL triggers exception */
    if (top == MAXSTACK)                         /* python sets arg-error msg */
        onError("stack overflow")                /* iff maxstack < maxchars */
    if (len + strlen(pstr) + 1 >= MAXCHARS)
        onError("string-space overflow")
    else {
        strcpy(strings + len, pstr);             /* store in string-space */
        stack[top++] = &(strings[len]);          /* push start address */
        len += (strlen(pstr) + 1);               /* new string-space size */
        Py_INCREF(Py_None);                      /* a 'procedure' call */
        return Py_None;                          /* None: no errors */
    }
}

static PyObject *
stack_pop(PyObject *self, PyObject *args)
{                                                /* no arguments for pop */
    PyObject *pstr;
    if (!PyArg_ParseTuple(args, ""))             /* verify no args passed */
        return NULL;
    if (top == 0)
        onError("stack underflow")               /* return NULL = raise */
    else {
        pstr = Py_BuildValue("s", stack[--top]); /* convert result: C->Py */
        len -= (strlen(stack[top]) + 1);
        return pstr;                             /* return new python string */
    }                                            /* pstr ref-count++ already */
}

static PyObject *
stack_top(PyObject *self, PyObject *args)        /* almost same as item(-1) */
{                                                /* but different errors */
    PyObject *result = stack_pop(self, args);    /* get top string */
    if (result != NULL)
        len += (strlen(stack[top++]) + 1);       /* undo pop */
    return result;                               /* NULL or string object */
}

static PyObject *
stack_empty(PyObject *self, PyObject *args)      /* no args: '(  )' */
{
    if (!PyArg_ParseTuple(args, ""))             /* or PyArg_NoArgs */
        return NULL;
    return Py_BuildValue("i", top == 0);         /* boolean: a python int */
}

static PyObject *
stack_member(PyObject *self, PyObject *args)
{
    int i;
    char *pstr;
    if (!PyArg_ParseTuple(args, "s", &pstr))
        return NULL;      
    for (i = 0; i < top; i++)                /* find arg in stack */
        if (strcmp(pstr, stack[i]) == 0)
            return PyInt_FromLong(1);        /* send back a python int */
    return PyInt_FromLong(0);                /* same as Py_BuildValue("i" */
}

static PyObject *
stack_item(PyObject *self, PyObject *args)    /* return Python string or NULL */
{                                             /* inputs = (index): Python int */
    int index;
    if (!PyArg_ParseTuple(args, "i", &index))    /* convert args to C */
        return NULL;                             /* bad type or arg count? */
    if (index < 0)
        index = top + index;                     /* negative: offset from end */
    if (index < 0 || index >= top)
        onError("index out-of-bounds")           /* return NULL = 'raise' */
    else 
        return Py_BuildValue("s", stack[index]); /* convert result to Python */
}                                                /* no need to INCREF new obj */

static PyObject *
stack_len(PyObject *self, PyObject *args)     /* return a Python int or NULL */
{                                             /* no inputs */
    if (!PyArg_ParseTuple(args, ""))          
        return NULL;      
    return PyInt_FromLong(top);               /* wrap in python object */
}

static PyObject *
stack_dump(PyObject *self, PyObject *args)    /* not "print": reserved word */
{
    int i;
    if (!PyArg_ParseTuple(args, ""))
        return NULL;
    printf("[Stack:\n");
    for (i=top-1; i >= 0; i--)                   /* formatted output */
        printf("%d: '%s'\n", i, stack[i]);
    printf("]\n");
    Py_INCREF(Py_None);
    return Py_None;
}

/******************************************************************************
* METHOD REGISTRATION TABLE: NAME-STRING -> FUNCTION-POINTER
******************************************************************************/

static struct PyMethodDef stack_methods[] = {
 {"push",       stack_push,     1},                /* name, address */
 {"pop",        stack_pop,      1},                /* '1'=always tuple args */
 {"top",        stack_top,      1},
 {"empty",      stack_empty,    1},
 {"member",     stack_member,   1},
 {"item",       stack_item,     1},
 {"len",        stack_len,      1},
 {"dump",       stack_dump,     1},
 {NULL,         NULL}                              /* end, for initmodule */
};

/******************************************************************************
* INITIALIZATION FUNCTION (IMPORT-TIME)
******************************************************************************/

void
initstackmod(  )
{
    PyObject *m, *d;

    /* create the module and add the functions */
    m = Py_InitModule("stackmod", stack_methods);        /* registration hook */

    /* add symbolic constants to the module */
    d = PyModule_GetDict(m);
    ErrorObject = Py_BuildValue("s", "stackmod.error");  /* export exception */
    PyDict_SetItemString(d, "error", ErrorObject);       /* add more if need */
    
    /* check for errors */
    if (PyErr_Occurred(  ))
        Py_FatalError("can't initialize module stackmod");
}

This C extension file is compiled and statically or dynamically linked with the interpreter just like in previous examples. File makefile.stack on the CD (see http://examples.oreilly.com/python2) handles the build with a rule like this:

stackmod.so: stackmod.c
    gcc stackmod.c -g -I$(PY)/Include -I$(PY) -fpic -shared -o stackmod.so

The whole point of implementing such a stack in a C extension module (apart from demonstrating API calls in a Python book) is optimization: in theory, this code should present a similar interface to the Python stack module we wrote earlier, but run considerably faster due to its C coding. The interface is roughly the same, though we've sacrificed some Python flexibility by moving to C -- there are limits on size and stackable object types:

[mark@toy ~/.../PP2E/Integrate/Extend/Stacks]$ python
>>> import stackmod                                      # load C module
>>> stackmod.push('new')                                 # call C functions
>>> stackmod.dump(  )                                      # dump format differs
[Stack:
0: 'new'
]
>>> for c in "SPAM": stackmod.push(c)
...
>>> stackmod.dump(  )
[Stack:
4: 'M'
3: 'A'
2: 'P'
1: 'S'
0: 'new'
]
>>> stackmod.len(), stackmod.top(  )
(5, 'M')
>>> x = stackmod.pop(  )
>>> x
'M'
>>> stackmod.dump(  )
[Stack:
3: 'A'
2: 'P'
1: 'S'
0: 'new'
]
>>> stackmod.push(99)
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: argument 1: expected string, int found

Some of the C stack's type and size limitations could be removed by alternate C coding (which might eventually create something that looks and performs almost exactly like a Python built-in list). Before we check on this stack's speed, though, we'll see what can be done about also optimizing our stack classes with a C type.

19.6.1 But Don't Do That Either -- SWIG

You can manually code extension modules like this, but you don't necessarily have to. As we saw earlier, if you instead code the stack module's functions without any notion of Python integration, they can be integrated into Python automatically by running their type signatures through SWIG. I haven't coded these functions that way here, because I also need to teach the underlying Python C extension API. But if I were asked to write a C string stack for Python in any other context, I'd do it with SWIG instead.

    I l@ve RuBoard Previous Section Next Section