Recipe 17.1. Implementing a Simple Extension Type
Credit: Alex Martelli
Problem
You want to code and build a C
extension type for Python with a minimal amount of hard work.
Solution
First of all, we need to create a setup.py file
to use the distutils package to build and install
our module:
from distutils.core import setup, ExtensionThen, we need a file elemlist.c with our
setup(name = "elemlist",
version = "1.0",
maintainer = "Alex Martelli",
maintainer_email = "amcx@aleax.it",
description = "Sample, simple Python extension module",
ext_modules = [Extension('elemlist',sources=['elemlist.c'])]
)
module's source code:
#include "Python.h"
/* type-definition and utility-macros */
typedef struct {
PyObject_HEAD
PyObject *car, *cdr;
} cons_cell;
staticforward PyTypeObject cons_type;
/* a type-testing macro (we don't actually use it here) */
#define is_cons(v) ((v)->ob_type == &cons_type)
/* utility macros to access car and cdr,
as either lvalues or rvalues */
#define carof(v) (((cons_cell*)(v))->car)
#define cdrof(v) (((cons_cell*)(v))->cdr)
/* ctor ("internal" factory-function) and dtor */
static cons_cell*
cons_new(PyObject *car, PyObject *cdr)
{
cons_cell *cons = PyObject_New(cons_cell, &cons_type);
if(cons) {
cons->car = car; Py_INCREF(car); /* INCREF when holding a PyObject */
cons->cdr = cdr; Py_INCREF(cdr); /* ditto */
}
return cons;
}
static void
cons_dealloc(cons_cell* cons)
{
/* DECREF when releasing previously-held PyObject*'s */
Py_DECREF(cons->car); Py_DECREF(cons->cdr);
PyObject_Del(cons);
}
/* A minimal Python type-object */
statichere PyTypeObject cons_type = {
PyObject_HEAD_INIT(0)
/* initialize to 0 to ensure Win32 portability */
0, /* ob_size */
"cons", /* tp_name */
sizeof(cons_cell), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)cons_dealloc, /* tp_dealloc */
/* implied by ISO C: all zeros thereafter, i.e., no other method */
};
/* module-functions */
static PyObject*
cons(PyObject *self, PyObject *args)
/* the exposed factory-function */
{
PyObject *car, *cdr;
if(!PyArg_ParseTuple(args, "OO", &car, &cdr))
return 0;
return (PyObject*)cons_new(car, cdr);
}
static PyObject*
car(PyObject *self, PyObject *args) /* car-accessor */
{
PyObject *cons;
if(!PyArg_ParseTuple(args, "O!", &cons_type, &cons))
/* type-checked */
return 0;
return Py_BuildValue("O", carof(cons));
}
static PyObject*
cdr(PyObject *self, PyObject *args) /* cdr-accessor */
{
PyObject *cons;
if(!PyArg_ParseTuple(args, "O!", &cons_type, &cons))
/* type-checked */
return 0;
return Py_BuildValue("O", cdrof(cons));
}
static PyObject*
setcar(PyObject *self, PyObject *args) /* car-setter */
{
PyObject *cons;
PyObject *value;
if(!PyArg_ParseTuple(args, "O!O", &cons_type, &cons, &value))
return 0;
Py_INCREF(value);
Py_DECREF(carof(cons));
carof(cons) = value;
return Py_BuildValue(");
}
static PyObject*
setcdr(PyObject *self, PyObject *args) /* cdr-setter */
{
PyObject *cons;
PyObject *value;
if(!PyArg_ParseTuple(args, "O!O", &cons_type, &cons, &value))
return 0;
Py_INCREF(value);
Py_DECREF(cdrof(cons));
cdrof(cons) = value;
return Py_BuildValue(");
}
static PyMethodDef elemlist_module_functions[ ] = {
{"cons", cons, METH_VARARGS},
{"car", car, METH_VARARGS},
{"cdr", cdr, METH_VARARGS},
{"setcar", setcar, METH_VARARGS},
{"setcdr", setcdr, METH_VARARGS},
{0, 0}
};
/* module entry-point (module-initialization) function */
void
initelemlist(void)
{
/* Create the module, with its functions */
PyObject *m = Py_InitModule("elemlist", elemlist_module_functions);
/* Finish initializing the type-objects */
cons_type.ob_type = &PyType_Type;
}
Discussion
C-coded Python extension types have an undeserved aura of mystery and
difficulty. Sure, it's a lot of work to implement
every possible feature, but a minimal yet useful type
doesn't necessarily take all that much effort.This module is roughly equivalent to the Python-coded module:
def cons(car, cdr): return car, cdrexcept that the C source is about 25 times larger, even excluding
def car(conscell): return conscell[0]
def cdr(conscell): return conscell[1]
def setcar(conscell, value): conscell[0] = value
def setcdr(conscell, value): conscell[1] = value
comments and empty lines (and it is not much faster than the
Python-coded version, either).However, the point of this recipe is to demonstrate a minimal C-coded
extension type. I'm not even supplying object
methods (except the indispensable destructor) but, rather, I am
providing module-level functions to build cons
cells and to read and write their car and
cdr fields. This recipe also shows the utter
simplicity of building a C-coded extension module on any platform,
thanks to the distutils package, which does all of
the hard work.Lisp-savvy readers will have recognized from the names involved that
this little extension offers the core functionality to implement a
Lisp-like linked list typeusing some NIL
marker (e.g. None), by convention, as the
cdr of the last cons-cell of a
list, and otherwise "consing up a
list" by having every cdr be
another cons-cell. You might easily
constrain the cdr to be
either None or another
cons-cell, giving up on generality for a bit of
extra error checking.Because this recipe is meant as an introduction to writing extension
modules in C for Python, here are the instructions for building this
extension module, assuming you have a Windows machine with Python 2.3
and Microsoft Visual C++ 6 (or the free command-line equivalent that
you can download from Microsoft's site as a part of
their .NET Framework SDK). You can presumably translate mentally to
other platforms such as Linux with gcc, Mac OS X
with gcc, and so on. On the other hand, using
different C compilers on Windows involves more work, and
I'm not going to cover that here (see http://sebsauvage.net/python/mingwl).Here are the steps you should follow to build this
recipe's extension:
- Make a new directoryfor example,
C:\Temp\EL. - Open a command-prompt window, and go to the new directory.
- In the new directory, create the files setup.py
and elemlist.c with the contents of the
recipe's text. - Run the following at the command prompt (assuming
you've performed a standard Python 2.3 installation,
so that your python.exe lives in
C:\Python23):<m>C:\Temp\EL> C:\Python23\python setup.py install</m>
- This command will result in lots of output, which you should examine
to check for problems. Presumably, all has gone well, and the new
elemlist extension module has been built and
installed. - Now try the extension by running the following at the DOS prompt:
<m>C:\Temp\EL> C:\Python23\python</m>
(snipped: various greeting messages from Python)
>>> from elemlist import cons, car, cdr
>>> a = cons(1, cons(2, cons(3, ( ))))
>>> car(cdr(a))
2
>>>
See Also
The Extending and Embedding manual is
available as part of the standard Python documentation set at
http://www.python.org/doc/current/ext/extl;
the section "Distributing Python
Modules" of the standard Python documentation set is
still incomplete, but it's a reliable source of
information on the distutils package.
Python in a Nutshell covers the essentials of
extending and embedding and of the distutils
package.