Lets 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
e 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. PP2EIntegrateExtendStacksstackmod.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 /* C header files */
#include
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 =
aise */
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:
");
for (i=top-1; i >= 0; i--) /* formatted output */
printf("%d: \%s\n", i, stack[i]);
printf("]
");
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 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:
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 weve 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(
ew) # call C functions
>>> stackmod.dump( ) # dump format differs
[Stack:
0:
ew
]
>>> for c in "SPAM": stackmod.push(c)
...
>>> stackmod.dump( )
[Stack:
4: M
3: A
2: P
1: S
0:
ew
]
>>> stackmod.len(), stackmod.top( )
(5, M)
>>> x = stackmod.pop( )
>>> x
M
>>> stackmod.dump( )
[Stack:
3: A
2: P
1: S
0:
ew
]
>>> stackmod.push(99)
Traceback (innermost last):
File "", line 1, in ?
TypeError: argument 1: expected string, int found
Some of the C stacks 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 stacks speed, though,
well see what can be done about also optimizing our stack
classes with a C
type.
.6.1 But Don Do That Either -- SWIG
You can manually code extension modules
like this, but you don necessarily have to. As we saw
earlier, if you instead code the stack modules functions
without any notion of Python integration, they can be integrated into
Python automatically by running their type signatures through SWIG. I
haven 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,
Id do it with SWIG instead.