Pyre Primer, Part 2
Writing Python Extensions using config (aka. the build procedure). Patrick Hung
Many tools exist for extending Python. (We will focus only on C Python, and will for now pay no attention to Jython).
To extend Python is to allow C/C++ programs to run under Python's control. We will note that the complemetary process (embedding), of allowing python codes to run under C/C++, is often useful, but will not be discussed here. See the primary documentation on extending and embedding the Python Interpreter for more details.
Writing extenion modules involve a fairly standard recipe, and many automated tools exist.
In many cases, using one of the above (or others) may be the right approach. In particular, using swig is very often the right choice.
However, doing things by hand can often be the easiest approach, precisely because there is a relatively standard recipe that one can follow in most cases.
In this regard, the right tool to compare the build procedure (our main topic, lest we forget) against is not with the ones listed above, but with Python's distutils.
In this section, we will describe a very simple and already quite useful example: wrapping Amtec's Tecplot using the Tecplot API. The API documentation can be found here. (This really is only Python wrapping at the beginner's level, more insights can be gained by reading ... Python's source code, or code from packages like vtk.)
When we are done, we will be able to save certain kinds[1] of Numpy arrays to binary tecplot format.
[1] This is part of a larger package that parses HDF4 files generated by the CFD framework of AMROC / DAGH.
The (pre version 10) API consists of
- TECDAT
- TECEND
- TECFIL
- TECGEO
- TECINI
- TECLAB
- TECNOD
- TECTXT
- TECZNE
Only the functions in red are wrapped as they are sufficient for my needs at this time.
Assuming that the package is to be called tecio, the basic directory structure is as follows:
| tecio/ | The root of the package | ||
| Make.mm | Main package makefile | ||
| libtecio/ | The library directory | ||
| hello.cc | |||
| hello.h | |||
| local.def | |||
| Make.mm | |||
| teciomodule/ | The extension module directory | ||
| dummy.cc | |||
| _tecio.cc | The extension module is to be called _tecio | ||
| local.def | |||
| Make.mm | |||
| tecio/ | |||
| __init__.py | |||
| Make.mm |
Note that the libtecio directory is basically inert. It is not needed in this case but it is simpler to leave it there in case other source files are needed for the package. All the "work" will be provided by the API's TECIO library.
The C extension's name will be specified in Make.mm under teciomodule. The content of Make.mm under teciomodule:
# -*- Makefile -*-
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Patrick Hung
# California Institute of Technology
#
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
PROJECT = tecio
PACKAGE = _tecio
MODULE = _tecio
include std-pythonmodule.def
include local.def
PROJ_CXX_SRCLIB = -ljournal -ltecio
PROJ_SRCS = \
dummy.cc \
# version
# $Id: Make.mm,v 1.1.1.1 2005/08/08 21:49:16 patrickh Exp $
# End of file
By defining the name of the module to be _tecio, the file _tecio.cc must exist. And according to the Python extension API, it must define the function init_tecio. More on this later.
The file local.def defines a few more variables: we will need Tecplot/default.def for obvious reasons. We will also be interfacing with Numeric (Numpy) and so we need to be able to find the Numeric include files.
# local.def # tecplot TECPLOT_LIBRARIES = tecio include Tecplot/default.def # Numeric NUMERIC_INCLUDES = $(NUMERIC_DIR)/Include EXERNAL_INCLUDES += $(NUMERIC_INCLUDES) # C++ PROJ_CXX_FLAGS = $(CXX_SOFLAGS) PROJ_LCXX_FLAGS = $(LCXX_SOFLAGS) # End of file
The head and the tail of _tecio.cc are, respectively,
// _tecio.cc
#include <Python.h>
#include "journal/debug.h"
#include "Numeric/arrayobject.h"
#include "TECIO.h"
static PyObject *_tecioError;
PyDoc_STRVAR(
module_doc,
"_tecio -- wrapping some of Tecplot's API functions\n"
"\n"
);
and
// List of functions defined in this module
struct PyMethodDef _tecio_methods[] = {
// the core routines
{ "hello", _tecio_hello, 1, hello_doc },
{ "TECINI", _tecio_tecini, 1, tecini_doc },
{ "TECZNE", _tecio_teczne, 1, teczne_doc },
{ "TECDAT", _tecio_tecdat, 1, tecdat_doc },
{ "TECFIL", _tecio_tecfil, 1, tecfil_doc },
{ "TECEND", _tecio_tecend, 1, tecend_doc },
{ NULL, NULL } // sentinel
};
PyMODINIT_FUNC init_tecio(void)
{
PyObject * m;
// Create the module and add the functions
m = Py_InitModule3( "_tecio", _tecio_methods, module_doc);
_tecioError = PyErr_NewException("_tecio.error", NULL, NULL);
Py_INCREF(_tecioError);
PyModule_AddObject(m, "error", _tecioError);
import_array(); // needed by Numeric Python
// check for errors
if (PyErr_Occurred()) {
Py_FatalError("can't initialize module _tecio");
}
return;
}
// end of file
