9 SWIG and Python

This chapter describes SWIG's support of Python. Many of the example presented here will have a scientific bias given Python's increasing use in scientific applications (this is how I primarily use Python), but the techniques are widely applicable to other areas.

Preliminaries

SWIG 1.1 works with Python 1.3 and Python 1.4. Given the choice, you should use the latest version of Python. You should also determine if your system supports shared libraries and dynamic loading. SWIG will work with or without dynamic loading, but the compilation process will vary.

Running SWIG

To build a Python module, run SWIG using the -python option :

%swig -python example.i

This will produce 2 files. The file example_wrap.c contains all of the C code needed to build a Python module and a documentation file describes the resulting interface. To build a Python module, you will need to compile the file example_wrap.c and link it with the rest of your program (and possibly Python itself). When working with shadow classes, SWIG will also produce a .py file, but this is described later.

Getting the right header files

In order to compile, you need to locate the following directories that are part of the Python distribution :

For Python 1.3 :

/usr/local/include/Py 
/usr/local/lib/python/lib

For Python 1.4 :

/usr/local/include/python1.4
/usr/local/lib/python1.4/config

The exact location may vary on your machine, but the above locations are typical.

Compiling a dynamic module

To build a shared object file, you need to compile your module in a manner similar to the following (shown for Irix):

% swig -python example.i
% gcc -c example.c
% gcc -c example_wrap.c -DHAVE_CONFIG_H -I/usr/local/include/python1.4 \
	-I/usr/local/lib/python1.4/config
% ld -shared example.o example_wrap.o -o examplemodule.so

Unfortunately, the process of building a shared object file varies on every single machine so you may need to read up on the man pages for your C compiler and linker.

When building a dynamic module, the name of the output file is important. If the name of your SWIG module is "example", the name of the corresponding object file should be "examplemodule.so" (or equivalent depending on your machine). The name of the module is specified using the %module directive or -module command line option.

While dynamic loading is the preferred method for making SWIG modules, it is not foolproof and not supported on all machines. In these cases, you can rebuild the Python interpreter with your extensions added.

Rebuilding the Python interpreter (aka. static linking)

The normal procedure for adding a new module to Python involves finding the Python source, adding an entry to the Modules/Setup file, and rebuilding the interpreter using the Python Makefile. While it's possible to simplify the process by using the VPATH feature of `make', I've always found the process to be a little too complicated.

SWIG provides an extremely easy, although somewhat unconventional, mechanism for rebuilding Python using SWIG's library feature. When you want to build a static version of Python, simply make an interface file like this :

%module example

extern int fact(int);
extern int mod(int, int);
extern double My_variable;

%include embed.i              // Include code for a static version of Python

The embed.i library file includes supporting code that contains everything needed to rebuild Python. To build your module, simply do the following :

% swig -python example.i
% gcc example.c example_wrap.c -DHAVE_CONFIG_H -I/usr/local/include/python1.4 \
	-I/usr/local/lib/python1.4/config \
	-L/usr/local/lib/python1.4/config -lModules -lPython -lObjects -lParser -lm \
	-o mypython

On some machines, you may need need to supply additional libraries on the link line. In particular, you may need to supply -lsocket, -lnsl, and -ldl.

It is also possible to add the embed.i library to an existing interface by running SWIG as follows :

% swig -python -lembed.i example.i

The embed.i file uses all of the modules that are currently being used in your installed version of Python. Thus, your new version of Python will be identical to the old one except with your new module added. If you have configured Python to use modules such as tkinter, you may need to supply linkage to the Tcl/Tk libraries and X11 libraries.

Python's main() program is rather unfriendly towards C++ code, but SWIG's embed.i module provides a replacement that can be compiled with the C++ compiler--making it easy to build C++ Python extensions.

The embed.i library should only be used with Python 1.4. If you are using Python 1.3, you should use the file embed13.i instead (this can be done by making a symbolic link in the SWIG library) or simply using the -l option.

Using your module

To use your module in Python, simply use Python's import command. The process is identical regardless of whether or not you used dynamic loading or rebuilt the Python interpreter :

% python
>>> import example
>>> example.fact(4)
24
>>>

Compilation problems and compiling with C++

For the most part, compiling a Python module is straightforward, but there are a number of potential problems :

Building Python Extensions under Windows 95/NT

Building a SWIG extension to Python under Windows 95/NT is roughly similar to the process used with Unix. Normally, you will want to produce a DLL that can be loaded into the Python interpreter. This section covers the process of using SWIG with Microsoft Visual C++ 4.x although the procedure may be similar with other compilers. SWIG currently supports both the basic Python release and Pythonwin. In order to build extensions, you will need to download the source distribution to these packages as you will need the Python header files.

Running SWIG from Developer Studio

If you are developing your application within Microsoft developer studio, SWIG can be invoked as a custom build option. The process roughly follows these steps :

Now, assuming all went well, SWIG will be automatically invoked when you build your project. Any changes made to the interface file will result in SWIG being automatically invoked to produce a new version of the wrapper file. To run your new Python extension, simply run Python and use the import command as normal. For example :

MSDOS > python
>>> import example
>>> print example.fact(4)
24
>>>

Using NMAKE

Alternatively, SWIG extensions can be built by writing a Makefile for NMAKE. Make sure the environment variables for MSVC++ are available and the MSVC++ tools are in your path. Now, just write a short Makefile like this :

# Makefile for building a Python extension

SRCS          = example.c
IFILE         = example
INTERFACE     = $(IFILE).i
WRAPFILE      = $(IFILE)_wrap.c

# Location of the Visual C++ tools (32 bit assumed)

TOOLS         = c:\msdev
TARGET        = example.dll
CC            = $(TOOLS)\bin\cl.exe
LINK          = $(TOOLS)\bin\link.exe
INCLUDE32     = -I$(TOOLS)\include
MACHINE       = IX86

# C Library needed to build a DLL

DLLIBC        = msvcrt.lib oldnames.lib  

# Windows libraries that are apparently needed
WINLIB        = kernel32.lib advapi32.lib user32.lib gdi32.lib comdlg32.lib 
winspool.lib

# Libraries common to all DLLs
LIBS          = $(DLLIBC) $(WINLIB) 

# Linker options
LOPT      = -debug:full -debugtype:cv /NODEFAULTLIB /RELEASE /NOLOGO \
             /MACHINE:$(MACHINE) -entry:_DllMainCRTStartup@12 -dll

# C compiler flags

CFLAGS        = /Z7 /Od /c /nologo
PY_INCLUDE    = -Id:\python-1.4\Include -Id:\python-1.4 -Id:\python-1.4\Pc
PY_LIB        = d:\python-1.4\vc40\python14.lib
PY_FLAGS = /D__WIN32__

python::
	swig -python -o $(WRAPFILE) $(INTERFACE)
	$(CC) $(CFLAGS) $(PY_FLAGS) $(PY_INCLUDE) $(SRCS) $(WRAPFILE)
	set LIB=$(TOOLS)\lib
	$(LINK) $(LOPT) -out:example.dll $(LIBS) $(PY_LIB) example.obj example_wrap.obj


To build the extension, run NMAKE (you may need to run vcvars32 first). This is a pretty simplistic Makefile, but hopefully its enough to get you started.

The low-level Python/C interface

The SWIG Python module is based upon a basic low-level interface that provides access to C functions, variables, constants, and C++ classes. This low-level interface is often used to create more sophisticated interfaces (such as shadow classes) so it may be hidden in practice.

Modules

The SWIG %module directive specifies the name of the Python module. If you specified `%module example', then everything found in a SWIG interface file will be contained within the Python `example' module. Make sure you don't use the same name as a built-in Python command or standard module or your results may be unpredictable.

Functions

C/C++ functions are mapped directly into a matching Python function. For example :

%module example
extern int fact(int n);

gets turned into the Python function example.fact(n) :

>>> import example
>>> print example.fact(4)
24
>>>

Variable Linking

SWIG provides access to C/C++ global variables, but the mechanism is slightly different than one might expect due to the object model used in Python. When you type the following in Python :

a = 3.4

"a" becomes a name for an object containing the value 3.4. If you later type

b = a

Then "a" and "b" are both names for the object containing the value 3.4. In other words, there is only one object containing 3.4 and "a" and "b" are both names that refer to it. This is a very different model than that used in C. For this reason, there is no mechanism for mapping "assignment" in Python onto C global variables (because assignment is Python is really a naming operation).

To provide access to C global variables, SWIG creates a special Python object called `cvar' that is added to each SWIG generated module. This object is used to access global variables as follows :

// SWIG interface file with global variables
%module example
...
extern int My_variable;
extern double density;
...

Now in Python :

>>> import example
>>> # Print out value of a C global variable
>>> print example.cvar.My_variable
4
>>> # Set the value of a C global variable
>>> example.cvar.density = 0.8442
>>> # Use in a math operation
>>> example.cvar.density = example.cvar.density*1.10

Just remember, all C globals need to be prefixed with a "cvar." and you will be set. If you would like to use a name other than "cvar", it can be changed using the -globals option :

% swig -python -globals myvar example.i

Some care is in order when importing multiple SWIG modules. If you use the "from <file> import *" style of importing, you will get a name clash on the variable `cvar' and will only be able to access global variables from the last module loaded. SWIG does not create cvar if there are no global variables in a module.

Constants

C/C++ constants are installed as new Python objects containing the appropriate value. These constants are given the same name as the corresponding C constant. "Constants" are not guaranteed to be constants in Python---in other words, you are free to change them and suffer the consequences!

Pointers

Pointers to C/C++ objects are represented as character strings such as the following :

_100f8e2_Vector_p

A NULL pointer is represented by the string "NULL". You can also explicitly create a NULL pointer consisting of the value 0 and a type such as :

_0_Vector_p

To some Python users, the idea of representing pointers as strings may seem strange, but keep in mind that pointers are meant to be opaque objects. In practice, you may never notice that pointers are character strings. There is also a certain efficiency in using this representation as it is easy to pass pointers around between modules and it is unnecessary to rely on a new Python datatype. Eventually, pointers may be represented as special Python objects, but the string representation works remarkably well so there has been little need to replace it.

Structures

The low-level SWIG interface only provides a simple interface to C structures. For example :

struct Vector {
	double x,y,z;
};

gets mapped into the following collection of C functions :

double Vector_x_get(Vector *obj)
double Vector_x_set(Vector *obj, double x)
double Vector_y_get(Vector *obj)
double Vector_y_set(Vector *obj, double y)
double Vector_z_get(Vector *obj)
double Vector_z_set(Vector *obj, double z)

These functions are then used in the resulting Python interface. For example :

# v is a Vector that got created somehow
>>> Vector_x_get(v)
3.5
>>> Vector_x_set(v,7.8)       # Change x component
>>> print Vector_x_get(v), Vector_y_get(v), Vector_z_get(v)
7.8 -4.5 0.0
>>> 

Similar access is provided for unions and the data members of C++ classes.

C++ Classes

C++ classes are handled by building a set of low level accessor functions. Consider the following class :

class List {
public:
  List();
  ~List();
  int  search(char *item);
  void insert(char *item);
  void remove(char *item);
  char *get(int n);
  int  length;
static void print(List *l);
};

When wrapped by SWIG, the following functions will be created :

List    *new_List();
void     delete_List(List *l);
int      List_search(List *l, char *item);
void     List_insert(List *l, char *item);
void     List_remove(List *l, char *item);
char    *List_get(List *l, int n);
int      List_length_get(List *l);
int      List_length_set(List *l, int n);
void     List_print(List *l);

Within Python, these functions used to access the C++ class :

>>> l = new_List()
>>> List_insert(l,"Ale")
>>> List_insert(l,"Stout")
>>> List_insert(l,"Lager")
>>> List_print(l)
Lager
Stout
Ale
>>> print List_length_get(l)
3
>>> print l
_1008560_List_p
>>>

C++ objects are really just pointers. Member functions and data are accessed by simply passing a pointer into a collection of accessor functions that take the pointer as the first argument.

While somewhat primitive, the low-level SWIG interface provides direct and flexible access to C++ objects. As it turns out, a more elegant method of accessing structures and classes is available using shadow classes.

Python shadow classes

The low-level interface generated by SWIG provides access to C structures and C++ classes, but it doesn't look much like a class that might be created in Python. However, it is possible to use the low-level C interface to write a Python class that looks like the original C++ class. In this case, the Python class is said to "shadow" the C++ class. That is, it behaves like the original class, but is really just a wrapper around a C++ class.

A simple example

For our earlier List class, a Python shadow class could be written by hand like this :

class List:
	def __init__(self):
		self.this = new_List()
	def __del__(self):
		delete_List(self.this)
	def search(self,item):
		return List_search(self.this,item)
	def insert(self,item):
		List_insert(self.this,item)
	def remove(self,item):
		List_remove(self.this,item)
	def get(self,n):
		return List_get(self.this,n)
	def __getattr__(self,name):
		if name == "length" : return List_length_get(self.this))
		else : return self.__dict__[name]
	def __setattr__(self,name,value):
		if name == "length": List_length_set(self.this,value)
		else : self.__dict__[name] = value

When used in a Python script, we can use the class as follows :


>>> l = List()
>>> l.insert("Ale")
>>> l.insert("Stout")
>>> l.insert("Lager")
>>> List_print(l.this)
Lager
Stout
Ale
>>> l.length
3

Obviously, this is a much nicer interface than before--and it only required a small amount of Python coding.

Why write shadow classes in Python?

While one could wrap C/C++ objects directly into Python as new Python types, this approach has a number of problems. First, as the C/C++ code gets complicated, the resulting wrapper code starts to become extremely ugly. It also becomes hard to handle inheritance and more advanced language features. A second, and more serious problem, is that Python "types" created in C can not be subclassed or used in the same way as one might use a real Python class. As a result, it is not possible to do interesting things like create Python classes that inherit from C++ classes.

By writing shadow classes in Python instead of C, the classes become real Python classes that can be used as base-classes in an inheritance hierarchy or for other applications. Writing the shadow classes in Python also greatly simplies coding complexity as writing in Python is much easier than trying to accomplish the same thing in C. Finally, by writing shadow classes in Python, they are easy to modify and can be changed without ever recompiling any of the C code. The downside to this approach is worse performance--a concern for some users.

The problems of combining C++ and Python have been of great interest to the Python community. SWIG is primarily concerned with accessing C++ from Python. Readers who are interested in more than this (and the idea of accessing Python classes from C++) are encouraged to look into the MESS extension which aims to provide a tighter integration between C++ and Python. The recently announced GRAD package also shows much promise and provides very comprehensive C++/Python interface.

Automated shadow class generation

SWIG can automatically generate shadow classes if you use the -shadow option :

swig -python -shadow interface.i

This will create the following two files :

interface_wrap.c
module.py

The file interface_wrap.c contains the normal SWIG C/C++ wrappers. The file module.py contains the Python code corresponding to shadow classes. The name of this file will be the same as specified by the %module directive in the SWIG interface file.

Associated with the two files are TWO Python modules. The C module `modulec' contains the low-level C interface that would have been created without the -shadow option. The Python module `module' contains the Python shadow classes that have been built around the low-level interface. To use the module, simply use `import module'. For all practical purposes, the `modulec' module is completely hidden although you can certainly use it if you want to.

Compiling modules with shadow classes

To compile a module involving shadow classes, you can use the same procedure as before except that the module name now has an extra `c' appended to the name. Thus, an interface file like this

%module example
... a bunch of declarations ...

might be compiled as follows :

% swig -python -shadow example.i
% gcc -c example.c example_wrap.c -I/usr/local/include/python1.4 \
	-I/usr/local/lib/python1.4/config -DHAVE_CONFIG_H
% ld -shared example.o example_wrap.o -o examplecmodule.so

Notice the naming of `examplecmodule.so' as opposed to `examplemodule.so' that would have been created without shadow classes.

When using static linking, no changes need to be made to the compilation process.

Where to go for more information

Shadow classes turn out to be so useful that they are used almost all of the time with SWIG. All of the examples presented here will assume that shadow classes have been enabled. The precise implementation of shadow classes is described at the end of this chapter and is not necessary to effectively use SWIG.

About the Examples

The next few sections will go through a series of Python examples of varying complexity. These examples are designed to illustrate how SWIG can be used to integrate C/C++ and Python in a variety of ways. Some of the things that will be covered include :

Solving a simple heat-equation

In this example, we will show how Python can be used to control a simple physics application--in this case, some C++ code for solving a 2D heat equation. This example is probably overly simplistic, but hopefully it's enough to give you some ideas.

The C++ code

Our simple application consists of the following two files :

// File : pde.h
// Header file for Heat equation solver

#include <math.h>
#include <stdio.h>

// A simple 2D Grid structure 

// A simple structure for holding a 2D grid of values
struct Grid2d {
  Grid2d(int ni, int nj);
  ~Grid2d();
  double **data;
  int      xpoints;
  int      ypoints;
};

// Simple class for solving a heat equation */
class Heat2d {
private:
  Grid2d    *work;                         // Temporary grid, needed for solver
  double    h,k;                           // Grid spacing
public:    
  Heat2d(int ni, int nj); 
  ~Heat2d();
  Grid2d    *grid;                         // Data
  double     dt;                           // Timestep
  double     time;                         // Elapsed time
  void       solve(int nsteps);            // Run for nsteps
  void       set_temp(double temp);        // Set temperature
};

The supporting C++ code implements a simple partial differential equation solver and some operations on the grid data structure. The precise implementation isn't important here, but all of the code can be found in the "Examples/python/manual" directory of the SWIG distribution.

Making a quick and dirty Python module

Given our simple application, making a Python module is easy. Simply use the following SWIG interface file :

// File : pde.i
%module pde
%{
#include "pde.h"
%}

%include pde.h

Since pde.h is fairly simple, we can simply include it directly into our interface file using %include. However, we also need to make sure we also include it in the %{,%} block--otherwise we'll get a huge number of compiler errors when we compile the resulting wrapper file.

To build the module simply run SWIG with the following options

swig -python -shadow pde.i

and compile using the techniques described in the beginning of this chapter.

Using our new module

We are now ready to use our new module. To do this, we can simply write a Python script like this :

# A fairly uninteresting example

from pde import *

h = Heat2d(50,50)          # Creates a new "problem"

h.set_temp(1.0)
print "Dt = ", h.dt

# Solve something

for i in range(0,25):	
	h.solve(100)
	print "time = ", h.time

When run, we get rather exciting output such as the following :

Dt =  2.5e-05
time =  0.0025
time =  0.005
time =  0.0075
...
time =  0.06
time =  0.0625

(okay, it's not that exciting--well, maybe it is if you don't get out much).

While this has only been a simple example it is important to note that we could have just as easily written the same thing in C++. For example :

// Python example written in C++

#include "pde.h"
#include <stdio.h>

int main(int argc, char **argv) {

  Heat2d *h;

  h = new Heat2d(50,50);
  printf("Dt = %g\n", h->dt);
  
  h->set_temp(1.0);

  for (int i = 0; i < 25; i++) {
    h->solve(100);
    printf("time = %g\n", h->time);
  }
}

For the most part, the code looks identical (although the Python version is simpler). As for performance, the Python version runs less than 1% slower than the C++ version on my machine. Given that most of the computational work is written in C++, there is very little performance penalty for writing the outer loop of our calculation in Python in this case.

Unfortunately, our Python version suffers a number of drawbacks. Most notably, there is no way for us to access any of the grid data (which is easily accomplished in C++). However, there are ways to fix this :

Accessing array data

Let's modify our heat equation problem so that we can access grid data directly from Python. This can be done by modifying our interface file as follows :

%module pde
%{
#include "pde.h"
%}

%include pde.h

// Add a few "helper" functions to extract grid data 
%inline %{
double  Grid2d_get(Grid2d *g, int i, int j) {
      return g->data[i][j];
}
void    Grid2d_set(Grid2d *g, int i, int j, double val) {
      g->data[i][j] = val;
}
%}

Rather than modifying our C++ code, it is easy enough to supply a few accessor functions directly in our interface file. These function may only be used from Python so this approach makes sense and it helps us keep our C++ code free from unnecessary clutter. The %inline directive is a convenient method for adding helper functions since the functions you declare show up in the interface automatically.

We can now use our accessor functions to write a more sophisticated Python script :

# An example using our set/get functions

from pde import *

# Set up an initial condition
def initcond(h):
	h.set_temp(0.0)
	nx = h.grid.xpoints
	for i in range(0,nx):
		Grid2d_set(h.grid,i,0,1.0)                        # Set grid values

# Dump out to a file
def dump(h,filename):
	f = open(filename,"w")
	nx = h.grid.xpoints
	ny = h.grid.ypoints
	for i in range(0,nx):
		for j in range(0,ny):
			f.write(str(Grid2d_get(h.grid,i,j))+"\n")  # Get grid value
	f.close()

# Set up a problem and run it

h = Heat2d(50,50)
initcond(h)
fileno = 1
for i in range(0,25):
	h.solve(100)
	dump(h,"Dat"+str(fileno))
	print "time = ", h.time
	fileno = fileno+1

We now have a Python script that can create a grid, set up an initial condition, run a simulation, and dump a collection of datafiles. So, with just a little supporting code in our interface file, we can start to do useful work from Python.

Use Python for control, C for performance

Now that it is possible to access grid data from Python, it is possible to quickly write code for all sorts of operations. However, Python may not provide enough performance for certain operations. For example, the dump() function in the previous example may become quite slow as problem sizes increase. Thus, we might consider writing it in C++ such as the follows:

void dump(Heat2d *h, char *filename) {
      FILE *f;
      int   i,j;

      f = fopen(filename,"w");
      for (i = 0; i < h->grid->xpoints; i++)
	for (j = 0; j < h->grid->ypoints; j++)
	  fprintf(f,"%0.17f\n",h->grid->data[i][j]);
      fclose(f);
}

To use this new function, simple put its declaration in the SWIG interface file and get rid of the old Python version. The Python script won't know that you changed the implementation.

Getting even more serious about array access

We have provided access to grid data using a pair of get/set functions. However, using these functions is a little clumsy because they always have to be called as a separate function like this :

Grid2d_set(grid,i,j,1.0)

It might make more sense to make the get/set functions appear like member functions of the Grid2D class. That way we could use them like this :

grid.set(i,j,1.0)
grid.get(i,j)

SWIG provides a simple technique for doing this as illustrated in the following interface file :

%module pde
%{
#include "pde.h"
%}
%include pde.h

// Add a few "helper" functions to extract grid data 
%{
    double  Grid2d_get(Grid2d *g, int i, int j) {
      return g->data[i][j];
    }
    void    Grid2d_set(Grid2d *g, int i, int j, double val) {
      g->data[i][j] = val;
    }
%}

// Now add these helper functions as methods of Grid2d

%addmethods Grid2d {
    double get(int i, int j);              // Gets expanded to Grid2d_get()
    void   set(int i, int j, double val);  // Gets expanded to Grid2d_set()
}

The %addmethods directive tells SWIG that you want to add new functions to an existing C++ class or C structure for the purposes of building an interface. In reality, SWIG leaves the original C++ class unchanged, but the resulting Python interface will have some new functions that appear to be class members.

SWIG uses a naming convention for adding methods to a class. If you have a class Foo and you add a member function bar(args), SWIG will look for a function called Foo_bar(this,args) that implements the desired functionality. You can write this function yourself, as in the previous interface file, but you can also just supply the code immediately after a declaration like this :

%module pde
%{
#include "pde.h"
%}
%include pde.h

// Add some new accessor methods to the Grid2D class
%addmethods Grid2d {
  double get(int i, int j) {
    return self->data[i][j];
  };
  void set(int i, int j, double val) {
    self->data[i][j] = val;
  };
};

In this case, SWIG will take the supplied code, and automatically generate a function for the method. The special variable "self" is used to hold a pointer to the corresponding object. The self pointer is exactly like the C++ "this" pointer, except that the name has been changed in order to remind you that you aren't really writing a real class member function. (Actually, the real reason we can't use "this" is because the C++ compiler will start complaining!)

Finally, it is worth noting that the %addmethods directive may also be used inside a class definition like this :

struct Grid2d {
  Grid2d(int ni, int nj);
  ~Grid2d();
  double **data;
  int      xpoints;
  int      ypoints;
  %addmethods {
	double get(int i, int j);
	void   set(int i, int j, double value);
  }
};

This latter case is really only useful if the C++ class definition is included in the SWIG interface file itself. If you are pulling the class definition out of a separate file or a C++ header file, using a separate %addmethods directive is preferable. It doesn't matter if the %addmethods directive appears before or after the real class definition--SWIG will correctly associate the two definitions.

Okay, enough talk. By adding the set/get functions as methods, we can now change our Python script to look like this (changes are underlined) :

# An example using our new set/get functions

from pde import *

# Set up an initial condition

def initcond(h):
	h.set_temp(0.0)
	nx = h.grid.xpoints
	for i in range(0,nx):
		h.grid.set(i,0,1.0)         # Note changed interface

# Dump out to a file
def dump(h,filename):
	f = open(filename,"w")
	nx = h.grid.xpoints
	ny = h.grid.ypoints
	for i in range(0,nx):
		for j in range(0,ny):
			f.write(str(h.grid.get(i,j))+"\n")
	f.close()

# Set up a problem and run it

h = Heat2d(50,50)
initcond(h)
fileno = 1

for i in range(0,25):
	h.solve(100)
	h.dump("Dat"+str(fileno))
	print "time = ", h.time
	fileno = fileno+1

Now it's starting to look a little better, but we can do even better...

Implementing special Python methods in C

Now that you're getting into the spirit of things, let's make it so that we can access our Grid2D data like a Python array. As it turns out, we can do this with a little trickery in the SWIG interface file. Don't forget to put on your Python wizard cap...

// SWIG interface file with Python array methods added
%module pde
%{
#include "pde.h"
%}

%include pde.h

%inline %{
  // Define a new Grid2d row class 
  struct Grid2dRow {
    Grid2d *g;       // Grid
    int    row;      // Row number
    // These functions are used by Python to access sequence types (lists, tuples, ...)
    double __getitem__(int i) {
      return g->data[row][i];
    };
    void __setitem__(int i, double val) {
      g->data[row][i] = val;
    };
  };
%}

// Now add a __getitem__ method to Grid2D to return a row
%addmethods Grid2d {
  Grid2dRow __getitem__(int i) {
    Grid2dRow r;
    r.g = self;
    r.row = i;
    return r;
  };
};

We have now replaced our get/set functions with the __getitem__ and __setitem__ functions that Python needs to access arrays. We have also added a special Grid2dRow class. This is needed to allow us to make a funny kind of "multidimensional" array in Python (this may take a few minutes of thought to figure out). Using this new interface file, we can now write a Python script like this :

# An example script using our array access functions

from pde import *

# Set up an initial condition

def initcond(h):
	h.set_temp(0.0)
	nx = h.grid.xpoints
	for i in range(0,nx):
		h.grid[i][0] = 1.0            # Note nice array access

# Set up a problem and run it

h = Heat2d(50,50)
initcond(h)
fileno = 1

for i in range(0,25):
	h.solve(100)
	h.dump("Dat"+str(fileno))
	print "time = ", h.time
	fileno = fileno+1

# Calculate average temperature over the region

sum = 0.0
for i in range(0,h.grid.xpoints):
	for j in range(0,h.grid.ypoints):
		sum = sum + h.grid[i][j]        # Note nice array access

avg = sum/(h.grid.xpoints*h.grid.ypoints)

print "Avg temperature = ",avg

Summary (so far)

In our first example, we have taken a very simple C++ problem and wrapped it into a Python module. With a little extra work, we have been able to provide array type access to our C++ data from Python and to write some computationally intensive operations in C++. At this point, it would easy to write all sorts of Python scripts to set up problems, run simulations, look at the data, and to debug new operations implemented in C++.

Wrapping a C library

In this next example, we focus on wrapping the gd-1.2 library. gd is public domain library for fast GIF image creation written by Thomas Boutell and available on the internet. gd-1.2 is copyright 1994,1995, Quest Protein Database Center, Cold Spring Harbor Labs. This example assumes that you have gd-1.2 available, but you can use the ideas here to wrap other kinds of C libraries.

Preparing a module

Wrapping a C library into a Python module usually involves working with the C header files associated with a particular library. In some cases, a header file can be used directly (without modification) with SWIG. Other times, it may be necessary to copy the header file into a SWIG interface file and make a few touch-ups and modifications. In either case, it's usually not too difficult.

To make a module, you can use the following checklist :

In the case of the gd library, we can simply use the following SWIG interface file :

%module gd
%{
#include "gd.h"
%}

%section "gd-1.2",ignore
%include "gd.h"

// These will come in handy later
FILE *fopen(char *, char *);
void fclose(FILE *f);

In this file, we first tell SWIG to put all of the gd functions in a separate documentation section and to ignore all comments. This usually helps clean up the documentation when working with raw header files. Next, we simply include the contents of "gd.h" directly. Finally, we provide wrappers to fopen() and fclose() since these will come in handy in our Python interface.

If we give this interface file to SWIG, we will get the following output :

% swig -python -shadow  -I/usr/local/include gd.i
Generating wrappers for Python
/usr/local/include/gd.h : Line 32.  Arrays not currently supported (ignored).
/usr/local/include/gd.h : Line 33.  Arrays not currently supported (ignored).
/usr/local/include/gd.h : Line 34.  Arrays not currently supported (ignored).
/usr/local/include/gd.h : Line 35.  Arrays not currently supported (ignored).
/usr/local/include/gd.h : Line 41.  Arrays not currently supported (ignored).
/usr/local/include/gd.h : Line 42.  Arrays not currently supported (ignored).
%

While SWIG was able to handle most of the header file, it also ran into a few unsupported declarations---in this case, a few data structures with array members. However, the warning messages also tell us that these declarations have simply been ignored. Thus, we can choose to continue and build our interface anyways. As it turns out in this case, the ignored declarations are of little or no consequence so we can ignore the warnings.

If SWIG is unable to process a raw header file or if you would like to eliminate the warning messages, you can structure your interface file as follows :

%module gd
%{
#include "gd.h"
%}

%section "gd-1.2",ignore

... paste the contents of gd.h here and remove problems ...

// A few extra support functions

FILE *fopen(char *, char *);
void fclose(FILE *f);

This latter option requires a little more work (since you need to paste the contents of gd.h into the file and edit it), but is otherwise not much more difficult to do. For highly complex C libraries or header files that go overboard with the C preprocessor, you may need to do this more often.

Using the gd module

Now, that we have created a module from the gd library, we can use it in Python scripts. The following script makes a simple image of a black background with a white line drawn on it. Notice how we have used our wrapped versions of fopen() and fclose() to create a FILE handle for use in the gd library (there are also ways to use Python file objects, but this is described later).

# Simple gd program

from gd import *

im = gdImageCreate(64,64)
black = gdImageColorAllocate(im,0,0,0)
white = gdImageColorAllocate(im,255,255,255)
gdImageLine(im,0,0,63,63,white)
out = fopen("test.gif","w")
gdImageGif(im,out)
fclose(out)
gdImageDestroy(im)

That was simple enough--and it only required about 5 minutes of work. Unfortunately, our gd module still has a few problems...

Extending and fixing the gd module

While our first attempt at wrapping gd works for simple functions, there are a number of problems. For example, the gd-1.2 library contains the following function for drawing polygons :

void gdImagePolygon(gdImagePtr im, gdPointPtr points, int pointsTotal, int color);

The gdImagePtr type is created by another function in our module and the parameters pointsTotal and color are simple integers. However, the 2nd argument is a pointer to an array of points as defined by the following data structure in the gd-1.2 header file :

typedef struct {
	int x, y;
} gdPoint, *gdPointPtr;

Unfortunately, there is no way to create a gdPoint in Python and consequently no way to call the gdImagePolygon function. A temporary setback, but one that is not difficult to solve using the %addmethods directive as follows :

%module gd
%{
#include "gd.h"
%}

%include "gd.h"

// Fix up the gdPoint structure a little bit
%addmethods gdPoint {
  // Constructor to make an array of "Points"
  gdPoint(int npoints) {
    return (gdPoint *) malloc(npoints*sizeof(gdPoint));
  };
  // Destructor to destroy this array
  ~gdPoint() {
    free(self);
  };
  // Python method for array access
  gdPoint *__getitem__(int i) {
    return self+i;
  };
};

FILE *fopen(char *, char *);
void fclose(FILE *f);

With these simple additions, we can now create arrays of points and use the polygon function as follows :

# Simple gd program

from gd import *

im = gdImageCreate(64,64)
black = gdImageColorAllocate(im,0,0,0)
white = gdImageColorAllocate(im,255,255,255)

pts = gdPoint(3);                   # Create an array of Points
pts[0].x,pts[0].y = (5,5)           # Assign a set of points
pts[1].x,pts[1].y = (60,25)
pts[2].x,pts[2].y = (16,60)

gdImagePolygon(im,pts,3,white)      # Draw a polygon from our array of points
out = fopen("test.gif","w")
gdImageGif(im,out)
fclose(out)
gdImageDestroy(im)

Building a simple 2D imaging class

Now it's time to get down to business. Using our gd-1.2 module, we can write a simple 2D imaging class that hides alot of the underlying details and provides scaling, translations, and a host of other operations. (It's a fair amount code, but an interesting example of how one can take a simple C library and turn it into something that looks completely different).

# image.py
# Generic 2D Image Class
#
# Built using the 'gd-1.2' library by Thomas Boutell
#

import gd

class Image:
	def __init__(self,width,height,xmin=0.0,ymin=0.0,xmax=1.0,ymax=1.0):
		self.im = gd.gdImageCreate(width,height)
		self.xmin   = xmin
		self.ymin   = ymin
		self.xmax   = xmax
		self.ymax   = ymax
		self.width  = width
		self.height = height
		self.dx     = 1.0*(xmax-xmin)
		self.dy     = 1.0*(ymax-ymin)
		self.xtick  = self.dx/10.0
		self.ytick  = self.dy/10.0
		self.ticklen= 3
		self.name   = "image.gif"
		gd.gdImageColorAllocate(self.im,0,0,0)        # Black
		gd.gdImageColorAllocate(self.im,255,255,255)  # White
		gd.gdImageColorAllocate(self.im,255,0,0)      # Red
		gd.gdImageColorAllocate(self.im,0,255,0)      # Green
		gd.gdImageColorAllocate(self.im,0,0,255)      # Blue

	def __del__(self):
		print "Deleting"
		gd.gdImageDestroy(self.im)
	
	# Dump out this image to a file
	def write(self,name="NONE"):
		if name == "NONE":
			name = self.name
		f = gd.fopen(name,"w")
		gd.gdImageGif(self.im,f)
		gd.fclose(f)
		self.name = name
	
	# Virtual method that derived classes define
	def draw(self):
		print "No drawing method specified."

	# A combination of write and draw
	def show(self,filename="NONE"):
		self.draw()
		self.write(filename)

	# Load up a colormap from a Python array of (R,G,B) tuples
	def colormap(self, cmap):
		for i in range(0,255):
			gd.gdImageColorDeallocate(self.im,i)
		for c in cmap:
			gd.gdImageColorAllocate(self.im,c[0],c[1],c[2])

	# Change viewing region
	def region(self,xmin,ymin,xmax,ymax):
		self.xmin = xmin
		self.ymin = ymin
		self.xmax = xmax
		self.ymax = ymax
		self.dx     = 1.0*(xmax-xmin)
		self.dy     = 1.0*(ymax-ymin)

	# Transforms a 2D point into screen coordinates
	def transform(self,x,y):
		npt = []
		ix = (x-self.xmin)/self.dx*self.width + 0.5
		iy = (self.ymax-y)/self.dy*self.height + 0.5
		return (ix,iy)

	# A few graphics primitives
	def clear(self,color):
		gd.gdImageFilledRectangle(self.im,0,0,self.width,self.height,color)

	def plot(self,x,y,color):
		ix,iy = self.transform(x,y)
		gd.gdImageSetPixel(self.im,ix,iy,color)

	def line(self,x1,y1,x2,y2,color):
		ix1,iy1 = self.transform(x1,y1)
		ix2,iy2 = self.transform(x2,y2)
		gd.gdImageLine(self.im,ix1,iy1,ix2,iy2,color)

	def box(self,x1,y1,x2,y2,color):
		ix1,iy1 = self.transform(x1,y1)
		ix2,iy2 = self.transform(x2,y2)
		gd.gdImageRectangle(self.im,ix1,iy1,ix2,iy2,color)

	def solidbox(self,x1,y1,x2,y2,color):
		ix1,iy1 = self.transform(x1,y1)
		ix2,iy2 = self.transform(x2,y2)
		gd.gdImageFilledRectangle(self.im,ix1,iy1,ix2,iy2,color)
	
	def arc(self,cx,cy,w,h,s,e,color):
		ix,iy = self.transform(cx,cy)
		iw = (x - self.xmin)/self.dx * self.width
		ih = (y - self.ymin)/self.dy * self.height
		gd.gdImageArc(self.im,ix,iy,iw,ih,s,e,color)

	def fill(self,x,y,color):
		ix,iy = self.transform(x,y)
		gd.gdImageFill(self,ix,iy,color)

	def axis(self,color):
		self.line(self.xmin,0,self.xmax,0,color)
		self.line(0,self.ymin,0,self.ymax,color)
		x = -self.xtick*(int(-self.xmin/self.xtick)+1)
		while x <= self.xmax:
		    ix,iy = self.transform(x,0)
		    gd.gdImageLine(self.im,ix,iy-self.ticklen,ix,iy+self.ticklen,color)
		    x = x + self.xtick
		y = -self.ytick*(int(-self.ymin/self.ytick)+1)
		while y <= self.ymax:
		    ix,iy = self.transform(0,y)
		    gd.gdImageLine(self.im,ix-self.ticklen,iy,ix+self.ticklen,iy,color)
		    y = y + self.ytick

	# scalex(s).  Scales the x-axis.  s is given as a scaling factor
	def scalex(self,s):
		xc = self.xmin + self.dx/2.0
		dx = self.dx*s
		xmin = xc - dx/2.0
		xmax = xc + dx/2.0
		self.region(xmin,self.ymin,xmax,self.ymax)

	# scaley(s).  Scales the y-axis.  
	def scaley(self,s):
		yc = self.ymin + self.dy/2.0
		dy = self.dy*s
		ymin = yc - dy/2.0
		ymax = yc + dy/2.0
		self.region(self.xmin,ymin,self.xmax,ymax)

	# Zooms a current image.  s is given as a percent 
	def zoom(self,s):
		s = 100.0/s
		self.scalex(s)
		self.scaley(s)

	# Move image left.  s is given in range 0,100. 100 moves a full screen left
	def left(self,s):
		dx = self.dx*s/100.0
		xmin = self.xmin + dx
		xmax = self.xmax + dx
		self.region(xmin,self.ymin,xmax,self.ymax)

	# Move image right.  s is given in range 0,100. 100 moves a full screen right
	def right(self,s):
		self.left(-s)

	# Move image down.  s is given in range 0,100. 100 moves a full screen down
	def down(self,s):
		dy = self.dy*s/100.0
		ymin = self.ymin + dy
		ymax = self.ymax + dy
		self.region(self.xmin,ymin,self.xmax,ymax)

	# Move image up.  s is given in range 0,100. 100 moves a full screen up
	def up(self,s):
		self.down(-s)

	# Center image
	def center(self,x,y):
		self.right(50-x)
		self.up(50-y)
	

Our image class provides a number of methods for creating images, plotting points, making lines, and other graphical objects. We have also provided some methods for moving and scaling the image. Now, let's use this image class to do some interesting things :

A mathematical function plotter

Here's a simple class that can be used to plot mathematical functions :

# funcplot.py

from image import *

class PlotFunc(Image):
	def __init__(self,func,xmin,ymin,xmax,ymax,width=500,height=500):
		Image.__init__(self,width,height,xmin,ymin,xmax,ymax)
		self.func = func            # The function being plotted
		self.npoints = 100          # Number of samples
		self.color = 1
	def draw(self):
		self.clear(0)
		lastx = self.xmin
		lasty = self.func(lastx)
		dx = 1.0*(self.xmax-self.xmin)/self.npoints
		x = lastx+dx
		for i in range(0,self.npoints):
			y = self.func(x)
			self.line(lastx,lasty,x,y,self.color)
			lastx = x
			lasty = y
			x = x + dx
		self.axis(1)

Most of the functionality is implemented in our base image class so this is pretty simple. However, if we wanted to make a GIF image of a mathematical function, we could just do this :

>>> from funcplot import *
>>> import math
>>> p = PlotFunc(lambda x: 0.5*math.sin(x)+0.75*math.sin(2*x)-0.6*math.sin(3*x),
                 -10,-2,10,2)
>>> p.show("plot.gif")

Which would produce the following GIF image :

Plotting an unstructured mesh

Of course, perhaps we want to plot something a little more complicated like a mesh. Recently, a colleague came to me with some unstructured mesh data contained in a pair of ASCII formatted files. These files contained a collection of points, and a list of connectivities defining a mesh on these points. Reading and plotting this data in Python turned out to be relatively easy using the following script and our image base class :

# plotmesh.py
# Plots an unstructured mesh stored as an ASCII file
from image import *
import string

class PlotMesh(Image):
	def __init__(self,filename,xmin,ymin,xmax,ymax,width=500,height=500):
		Image.__init__(self,width,height,xmin,ymin,xmax,ymax)
		# read in a mesh file in pieces
		pts = []
		# Read in data points
		atoi = string.atoi
		atof = string.atof
		f = open(filename+".pts","r")
		npoints = atoi(f.readline())
		for i in range(0,npoints):
			l = string.split(f.readline())
			pts.append((atof(l[0]),atof(l[1])))
		f.close()	

		# Read in mesh data
		f = open(filename+".tris","r")
		ntris = string.atoi(f.readline())
		tris = [ ]
		for i in range(0,ntris):
			l = string.split(f.readline())
			tris.append((atoi(l[0])-1,atoi(l[1])-1,atoi(l[2])-1,atoi(l[3])))
		f.close()

	# Set up local attributes
		self.pts = pts
		self.npoints = npoints
		self.tris = tris
		self.ntris = ntris

	# Draw mesh
	def draw(self):
		self.clear(0);
		i = 0
		while i < self.ntris:
			tri = self.tris[i]
			pt1 = self.pts[tri[0]]
			pt2 = self.pts[tri[1]]
			pt3 = self.pts[tri[2]]
			# Now draw the mesh
			self.triangle(pt1[0],pt1[1],pt2[0],pt2[1],pt3[0],pt3[1],tri[3]);
			i = i + 1

	# Draw a triangle
	def triangle(self,x1,y1,x2,y2,x3,y3,color):
		self.line(x1,y1,x2,y2,color)
		self.line(x2,y2,x3,y3,color)
		self.line(x3,y3,x1,y1,color)

This class simply reads the data into a few Python lists, has a drawing function for making a plot, and adds a special method for making triangles. Making a plot is now easy, just do this :

>>> from plotmesh.py import *
>>> mesh = PlotMesh("mesh",5,0,35,25)
>>> mesh.show("mesh.gif")

This produces the following GIF image :

When run interactively, we can also use simple commands to zoom in and move the image around. For example :

>>> mesh = PlotMesh("mesh",5,0,35,25)
>>> mesh.zoom(200)                   # Enlarge by 200%
>>> mesh.left(50)                    # Move image half a screen to left
>>> mesh.show()
>>>

While a Python-only implementation would be unsuitable for huge datasets, performance critical operations could be moved to C and used in conjunction with our Image base class.

From C to SWIG to Python

This example illustrates a number of things that are possible with SWIG and Python. First, it is usually relatively easy to build a Python interface to an existing C library. With a little extra work, it is possible to improve the interface by adding a few support functions such as our Point extensions. Finally, once in Python, it is possible to encapsulate C libraries in new kinds of Python objects and classes. We built a simple Image base class and used it to plot mathematical functions and unstructured 2D mesh data---two entirely different tasks, yet easily accomplished with a small amount of Python code. If we later decided to use a different C library such as OpenGL, we could wrap it in SWIG, change the Image base class appropriately , and use the function and mesh plotting examples without modification. I think this is pretty cool.

Putting it all together

Finally, let's combine our heat equation solver and graphics module into a single application. To do this, we first need to know how to combine two different SWIG generated modules. When different SWIG modules need to be combined, there are a number of things you can do.

Merging modules

Two SWIG modules can be combined into a single module if you make an interface file like this :

%module package
%include pde.i
%include gd.i

This will combine everything in both interface files into a single super-module called "package". The advantage to this approach is that it is extremely quick and easy. The disadvantage is that the module names of "pde" and "gd" will be lost. If you had a bunch of scripts that relied on those names, they would no longer work. Thus, combining modules in this way is probably only a good idea if the modules are closely related.

Using dynamic loading

If your system supports dynamic loading, you can build each SWIG module into a separate dynamically loadable module and load each one individually into Python. This is the preferred approach if it is supported on your system. SWIG wrapper files declare virtually everything as "static" so using dynamic loading with multiple SWIG generated modules will not usually cause any namespace clashes.

Use static linking

As an alternative to dynamic loading, you can use a special form of the %module directive as follows :

%module package, pdec, gdc
%include embed.i

This will build a static version of Python with 3 C extension modules added (package, pdec, and gdc). When using this technique, the names of the modules refer to the low-level SWIG generated C/C++ modules. Since shadow classes are being used, these modules must have an extra `c' appended to the name (thus, "pdec" and "gdc" instead of "pde" and "gd"). The extra modules specified with the %modules directive do not necessarily have to be SWIG-generated modules. In practice, almost any kind of Python module can be listed here. It should also be noted that extra modules names are completely ignored if the embed.i library file is not used.

Building large multi-module systems

By default, SWIG includes the C code for the SWIG type-checker and variable linking into every module. However, when, building systems involving large numbers of SWIG modules, common code such as the SWIG pointer type-checker and variable linking extensions can be shared if you run SWIG with the -c option. For example :

% swig -c -python graphics.i
% swig -c -python network.i
% swig -c -python analysis.i
% swig -c -python math.i
% gcc -c graphics_wrap.c network_wrap.c analysis_wrap.c math_wrap.c
% ld -shared graphics_wrap.o -lswigpy -o graphicsmodule.so 
% ld -shared network_wrap.o -lswigpy -o networkmodule.so 
% ld -shared analysis_wrap.o -lswigpy -o analysismodule.so 
% ld -shared math_wrap.o -o -lswigpy mymathmodule.so 

swigpy is a special purpose library that contains the SWIG pointer type checker and other support code (see the Misc subdirectory of the SWIG distribution). When used in this manner, the same support code will be used for all of the modules. The swigpy library can also be applied when static linking is being used. See the Advanced Topics chapter for more information about using SWIG with multiple modules.

A complete application

The following Python script shows an application that combines our C++ heat equation solver, our gd library, and our Image base class that we developed.

# Solve the heat equation.
# Make a series of data files
# Make a movie of GIF images

from pde import *
from image import *
import string

# Image class
class HeatImg(Image):
	def __init__(self,h,width=300,height=300):
		Image.__init__(self,width,height,0.0,0.0,1.0,1.0)
		self.h = h
		# Create a greyscale colormap
		cmap = []
		for i in range(0,255):
			cmap.append((i,i,i))
		self.colormap(cmap)
		self.cmin = 0.0
		self.cmax = 1.0
		self.imgno = 1
	def draw(self):
		self.clear(0)
		dx = 1.0/(self.h.grid.xpoints-2)
		dy = 1.0/(self.h.grid.ypoints-2)
		i = 1
		x = 0.0
		while i < self.h.grid.xpoints:
			j = 1;
			y = 0.0
			while j < self.h.grid.ypoints:
				c = int((self.h.grid[i][j]-self.cmin)/(self.cmax- 
					 self.cmin)*255)
				self.solidbox(x,y+dy,x+dx,y,c)
				j = j + 1
				y = y + dy
			i = i + 1
			x = x + dx
		self.name = "image"+string.zfill(self.imgno,4)+".gif"
		self.imgno = self.imgno+1

# Set up an initial condition
def initcond(h):
	h.set_temp(0.0)
	nx = h.grid.xpoints
	for i in range(0,nx):
		h.grid[i][0] = 1.0

# Set up a problem and run it
h = Heat2d(50,50)

# Make an image object
img = HeatImg(h)

initcond(h)
fileno = 1

# Run it
for i in range(0,25):
	h.solve(100)
	h.dump("Dat"+str(fileno))
	img.show()
	print "time = ", h.time
	fileno = fileno+1

# Calculate average temperature and exit
sum = 0.0
for i in range(0,h.grid.xpoints):
	for j in range(0,h.grid.ypoints):
		sum = sum + h.grid[i][j]
avg = sum/(h.grid.xpoints*h.grid.ypoints)
print "Avg temperature = ",avg

When run, we now get a collection of datafiles and series of images like this :

Thus, we have a simple physics application that only takes about 1 page of Python code, runs a simulation, creates data files, and a movie of images. We can easily change any aspect of the simulation, interactively query variables and examine data. New procedures can be written and tested in Python and later implemented in C++ if needed. More importantly, we have an application that is actually fun to use and modify (well, at least I think so).

Exception handling

The SWIG %except directive can be used to create a user-definable exception handler in charge of converting exceptions in your C/C++ program into Python exceptions. The chapter on exception handling contains more details, but suppose you have a C++ class like the following :

class RangeError {};   // Used for an exception

class DoubleArray {
  private:
    int n;
    double *ptr;
  public:
    // Create a new array of fixed size
    DoubleArray(int size) {
      ptr = new double[size];
      n = size;
    }
    // Destroy an array
    ~DoubleArray() {
       delete ptr;
    }
    // Return the length of the array
    int   length() {
      return n;
    }

    // Get an item from the array and perform bounds checking.
    double getitem(int i) {
      if ((i >= 0) && (i < n))
        return ptr[i];
      else
        throw RangeError();
    }

    // Set an item in the array and perform bounds checking.
    void setitem(int i, double val) {
      if ((i >= 0) && (i < n))
        ptr[i] = val;
      else {
        throw RangeError();
      }
    }
  };

The functions associated with this class can throw a range exception for an out-of-bounds array access. We can catch this in our Python extension by specifying the following in an interface file :

%except(python) {
  try {
    $function
  }
  catch (RangeError) {
    PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
    return NULL;
  }
}

When the C++ class throws a RangeError exception, our wrapper functions will catch it, turn it into a Python exception, and allow a graceful death as opposed to just having some sort of mysterious program crash. Since SWIG's exception handling is user-definable, we are not limited to C++ exception handling. Please see the chapter on exception handling for more details and using the exception.i library for writing language-independent exception handlers.

Python exceptions can be raised using the PyErr_SetString() function as shown above. The following table provides a list of the different Python exceptions available.

Remapping C datatypes with typemaps

This section describes how SWIG's treatment of various C/C++ datatypes can be remapped using the SWIG %typemap directive. While not required, this section assumes some familiarity with Python's C API. The reader is advised to consult the Python reference manual or one of the books on Python. A glance at the chapter on SWIG typemaps will also be useful.

What is a typemap?

A typemap is mechanism by which SWIG's processing of a particular C datatype can be overridden. A simple typemap might look like this :

%module example

%typemap(python,in) int {
	$target = (int) PyLong_AsLong($source);
	printf("Received an integer : %d\n",$target);
}
extern int fact(int n);

Typemaps require a language name, method name, datatype, and conversion code. For Python, "python" should be used as the language name. The "in" method in this example refers to an input argument of a function. The datatype `int' tells SWIG that we are remapping integers. The supplied code is used to convert from a PyObject * to the corresponding C datatype. Within the supporting C code, the variable $source contains the source data (the PyObject in this case) and $target contains the destination of a conversion.

When this example is compiled into a Python module, it will operate as follows :

>>> from example import *
>>> fact(6)
Received an integer : 6
720

A full discussion of typemaps can be found in the main SWIG users reference. We will primarily be concerned with Python typemaps here.

Python typemaps

The following typemap methods are available to Python modules :

%typemap(python,in) Converts Python objects to input function arguments

%typemap(python,out) Converts return value of a C function to a Python object

%typemap(python,varin) Assigns a global variable from a Python object

%typemap(python,varout) Returns a global variable as a Python object

%typemap(python,freearg) Cleans up a function argument (if necessary)

%typemap(python,argout) Output argument processing

%typemap(python,ret) Cleanup of function return values

%typemap(python,const) Creation of Python constants

%typemap(memberin) Setting of C++ member data

%typemap(memberout) Return of C++ member data

%typemap(python,check) Checks function input values.

Typemap variables

The following variables may be used within the C code used in a typemap:

$source Source value of a conversion

$target Target of conversion (where the result should be stored)

$type C datatype being remapped

$mangle Mangled version of data (used for pointer type-checking)

$value Value of a constant (const typemap only)

Name based type conversion

Typemaps are based both on the datatype and an optional name attached to a datatype. For example :

%module foo

// This typemap will be applied to all char ** function arguments
%typemap(python,in) char ** { ... }

// This typemap is applied only to char ** arguments named `argv'
%typemap(python,in) char **argv { ... }

In this example, two typemaps are applied to the char ** datatype. However, the second typemap will only be applied to arguments named `argv'. A named typemap will always override an unnamed typemap.

Due to the name-based nature of typemaps, it is important to note that typemaps are independent of typedef declarations. For example :

%typemap(python, in) double {
	... get a double ...
}
void foo(double);            // Uses the above typemap
typedef double Real;
void bar(Real);              // Does not use the above typemap (double != Real)

To get around this problem, the %apply directive can be used as follows :

%typemap(python,in) double {
	... get a double ...
}
void foo(double);

typedef double Real;         // Uses typemap
%apply double { Real };      // Applies all "double" typemaps to Real.
void bar(Real);              // Now uses the same typemap.

Converting Python list to a char **

A common problem in many C programs is the processing of command line arguments, which are usually passed in an array of NULL terminated strings. The following SWIG interface file allows a Python list object to be used as a char ** object.

%module argv

// This tells SWIG to treat char ** as a special case
%typemap(python,in) char ** {
  /* Check if is a list */
  if (PyList_Check($source)) {
    int size = PyList_Size($source);
    int i = 0;
    $target = (char **) malloc((size+1)*sizeof(char *));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($source,i);
      if (PyString_Check(o))
	$target[i] = PyString_AsString(PyList_GetItem($source,i));
      else {
	PyErr_SetString(PyExc_TypeError,"list must contain strings");
	free($target);
	return NULL;
      }
    }
    $target[i] = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

// This cleans up the char ** array we malloc'd before the function call
%typemap(python,freearg) char ** {
  free((char *) $source);
}

// This allows a C function to return a char ** as a Python list
%typemap(python,out) char ** {
  int len,i;
  len = 0;
  while ($source[len]) len++;
  $target = PyList_New(len);
  for (i = 0; i < len; i++) {
    PyList_SetItem($target,i,PyString_FromString($source[i]));
  }
}

// Now a few test functions
%inline %{
int print_args(char **argv) {
    int i = 0;
    while (argv[i]) {
         printf("argv[%d] = %s\n", i,argv[i]);
         i++;
    }
    return i;
}

// Returns a char ** list 

char **get_args() {
    static char *values[] = { "Dave", "Mike", "Susan", "John", "Michelle", 0};
    return &values[0];
}
%}

When this module is compiled, our wrapped C functions now operate as follows :

>>> from argv import *
>>> print_args(["Dave","Mike","Mary","Jane","John"])
argv[0] = Dave
argv[1] = Mike
argv[2] = Mary
argv[3] = Jane
argv[4] = John
5
>>> get_args()
[`Dave', `Mike', `Susan', `John', `Michelle']
>>>

Our type-mapping makes the Python interface to these functions more natural and easy to use.

Converting a Python file object to a FILE *

In our previous example involving gd-1.2, we had to write wrappers around fopen() and fclose() so that we could provide gd with a FILE * pointer. However, we could have used a typemap like this instead :

// Type mapping for grabbing a FILE * from Python

%typemap(python,in) FILE * {
  if (!PyFile_Check($source)) {
      PyErr_SetString(PyExc_TypeError, "Need a file!");
      return NULL;
  }
  $target = PyFile_AsFile($source);
}

Now, we can rewrite one of our earlier examples like this :

# Simple gd program

from gd import *

im = gdImageCreate(64,64)
black = gdImageColorAllocate(im,0,0,0)
white = gdImageColorAllocate(im,255,255,255)
gdImageLine(im,0,0,63,63,white)
f = open("test.gif","w")               # Create a Python file object
gdImageGif(im,f)                       # Pass to a C function as FILE *
f.close()
gdImageDestroy(im)

Using typemaps to return arguments

A common problem in some C programs is that values may be returned in arguments rather than in the return value of a function. For example :

/* Returns a status value and two values in out1 and out2 */
int spam(double a, double b, double *out1, double *out2) {
	... Do a bunch of stuff ...
	*out1 = result1;
	*out2 = result2;
	return status;
};

A named typemap can be used to handle this case as follows :

%module outarg

// This tells SWIG to treat an double * argument with name 'OutValue' as
// an output value.  We'll append the value to the current result which 
// is guaranteed to be a List object by SWIG.

%typemap(python,argout) double *OutValue {
	PyObject *o;
	o = PyFloat_FromDouble(*$source);
	if ((!$target) || ($target == Py_None)) {
		$target = o;
	} else {
		if (!PyList_Check($target)) {
			PyObject *o2 = $target;
			$target = PyList_New(0);
			PyList_Append($target,o2);
			Py_XDECREF(o2);
		}
		PyList_Append($target,o);
		Py_XDECREF(o);
	}
}
int spam(double a, double b, double *OutValue, double *OutValue);

With this typemap, we first check to see if any result exists. If so, we turn it into a list and append our new output value to it. If this is the only result, we simply return it normally. For our sample function, there are three output values so the function will return a list of 3 elements. As written, our function needs to take 4 arguments, the last two being pointers to doubles. We may not want to pass anything into these arguments if they are only used to hold output values so we could change this as follows :

%typemap(python,ignore) double *OutValue(double temp) {
	$target = &temp;       /* Assign the pointer to a local variable */
}

Now, in a Python script, we could do this :

>>> a = spam(4,5)
>>> print a
[0, 2.45, 5.0]
>>>

Mapping Python tuples into small arrays

In some applications, it is sometimes desirable to pass small arrays of numbers as arguments. For example :

extern void set_direction(double a[4]);       // Set direction vector

This too, can be handled used typemaps as follows :

// Grab a 4 element array as a Python 4-tuple
%typemap(python,in) double[4](double temp[4]) {   // temp[4] becomes a local variable
  int i;
  if (PyTuple_Check($source)) {
    if (!PyArg_ParseTuple($source,"dddd",temp,temp+1,temp+2,temp+3)) {
      PyErr_SetString(PyExc_TypeError,"tuple must have 4 elements");
      return NULL;
    }
    $target = &temp[0];
  } else {
    PyErr_SetString(PyExc_TypeError,"expected a tuple.");
    return NULL;
  }
}

This allows our set_direction function to be called from Python as follows :

>>> set_direction((0.5,0.0,1.0,-0.25))

Since our mapping copies the contents of a Python tuple into a C array, such an approach would not be recommended for huge arrays, but for small structures, this kind of scheme works fine.

Accessing array structure members

Consider the following data structure :

#define NAMELEN   32
typedef struct {
	char   name[NAMELEN];
	...
} Person;

By default, SWIG doesn't know how to the handle the name structure since it's an array, not a pointer. In this case, SWIG will make the array member readonly. However, member typemaps can be used to make this member writable from Python as follows :

%typemap(memberin) char[NAMELEN] {
	/* Copy at most NAMELEN characters into $target */
	strncpy($target,$source,NAMELEN);
}

Whenever a char[NAMELEN] type is encountered in a structure or class, this typemap provides a safe mechanism for setting its value. An alternative implementation might choose to print an error message if the name was too long to fit into the field.

It should be noted that the [NAMELEN] array size is attached to the typemap. A datatype involving some other kind of array would not be affected. However, you can write a typemap to match any sized array using the ANY keyword as follows :

%typemap(memberin) char [ANY] {
	strncpy($target,$source,$dim0);
}

During code generation, $dim0 will be filled in with the real array dimension.

Useful Functions

When writing typemaps, it is often necessary to work directly with Python objects instead of using the conventional PyArg_ParseTuple() function that is usually used when writing Python extensions. However, there are a number of useful Python functions available for you to use.







Standard typemaps

The following typemaps show how to convert a few common kinds of objects between Python and C (and to give a better idea of how typemaps work)



Pointer handling

SWIG pointers are mapped into Python strings containing the hexadecimal value and type. The following functions can be used to create and read pointer values.

These functions can be used in typemaps. For example, the following typemap makes an argument of "char *buffer" accept a pointer instead of a NULL-terminated ASCII string.

%typemap(python,in) char *buffer {
	PyObject *o;
	char     *str;
	if (!PyString_Check(o)) {
		PyErr_SetString(PyExc_TypeError,"not a string");
		return NULL;
	}
	str = PyString_AsString(o);
	if (SWIG_GetPtr(str, (void **) &$target, "$mangle")) {
		PyErr_SetString(PyExc_TypeError,"not a pointer");
		return NULL;
	}
}

Note that the $mangle variable generates the type string associated with the datatype used in the typemap.

By now you hopefully have the idea that typemaps are a powerful mechanism for building more specialized applications. While writing typemaps can be technical, many have already been written for you. See the Typemaps chapter for more information about using library files.

Implementing C callback functions in Python

Now that you're an expert, we will implement simple C callback functions in Python and use them in a C++ code.

Let's say that we wanted to write a simple C++ 2D plotting widget layered on top of the gd-1.2 library. A class definition might look like this :

// --------------------------------------------------------------------
// Create a C++ plotting "widget" using the gd-1.2 library by Thomas Boutell
//  
// This example primarily illustrates how callback functions can be
// implemented in Python.
// --------------------------------------------------------------------

#include <stdio.h>
extern "C" {
#include "gd.h"
}

typedef double (*PLOTFUNC)(double, void *);

class PlotWidget {
private:
  double      xmin,ymin,xmax,ymax;         // Plotting range
  PLOTFUNC    callback;                    // Callback function
  void       *clientdata;                  // Client data for callback
  int         npoints;                     // Number of points to plot
  int         width;                       // Image width
  int         height;                      // Image height
  int         black,white;                 // Some colors
  gdImagePtr  im;                          // Image pointer
  void        transform(double,double,int&,int&);
public:
  PlotWidget(int w, int h,double,double,double,double);
  ~PlotWidget();
  void set_method(PLOTFUNC func, void *clientdata);    // Set callback method
  void set_range(double,double,double,double);         // Set plot range
  void set_points(int np) {npoints = np;}              // Set number of points
  void plot();                                         // Make a plot
  void save(FILE *f);                                  // Save a plot to disk
};

The widget class hides all of the underlying implementation details so this could have just as easily been implemented on top of OpenGL, X11 or some other kind of library. When used in C++, the widget works like this :

// Simple main program to test out our widget
#include <stdio.h>
#include "widget.h"
#include <math.h>

// Callback function
double my_func(double a, void *clientdata) {
  return sin(a);
}

int main(int argc, char **argv) {
  PlotWidget *w;
  FILE *f;

  w = new PlotWidget(500,500,-6.3,-1.5,6.3,1.5);
  w->set_method(my_func,0);              // Set callback function
  w->plot();                             // Make plot
  f = fopen("plot.gif","w");
  w->save(f);
  fclose(f);
  printf("wrote plot.gif\n");
}

Now suppose that we wanted to use our widget interactively from Python. While possible, it is going to be difficult because we would really like to implement the callback function in Python, not C++. We also don't want to go in and hack or C++ code to support this. Fortunately, you can do it with SWIG using the following interface file :

// SWIG interface to our PlotWidget 
%module plotwidget
%{
#include "widget.h"
%}

// Grab a Python function object as a Python object.
%typemap(python,in) PyObject *pyfunc {
  if (!PyCallable_Check($source)) {
      PyErr_SetString(PyExc_TypeError, "Need a callable object!");
      return NULL;
  }
  $target = $source;
}

// Type mapping for grabbing a FILE * from Python
%typemap(python,in) FILE * {
  if (!PyFile_Check($source)) {
      PyErr_SetString(PyExc_TypeError, "Need a file!");
      return NULL;
  }
  $target = PyFile_AsFile($source);
}

// Grab the class definition
%include widget.h

%{
/* This function matches the prototype of the normal C callback
   function for our widget. However, we use the clientdata pointer
   for holding a reference to a Python callable object. */

static double PythonCallBack(double a, void *clientdata)
{
   PyObject *func, *arglist;
   PyObject *result;
   double    dres = 0;
   
   func = (PyObject *) clientdata;               // Get Python function
   arglist = Py_BuildValue("(d)",a);             // Build argument list
   result = PyEval_CallObject(func,arglist);     // Call Python
   Py_DECREF(arglist);                           // Trash arglist
   if (result) {                                 // If no errors, return double
     dres = PyFloat_AsDouble(result);
   }
   Py_XDECREF(result);
   return dres;
}
%}

// Attach a new method to our plot widget for adding Python functions
%addmethods PlotWidget {
   // Set a Python function object as a callback function
   // Note : PyObject *pyfunc is remapped with a typempap
   void set_pymethod(PyObject *pyfunc) {
     self->set_method(PythonCallBack, (void *) pyfunc);
     Py_INCREF(pyfunc);
   }
}

While this is certainly not a trivial SWIG interface file, the results are quite cool. Let's try out our new Python module :

# Now use our plotting widget in variety of ways

from plotwidget import *
from math import *

# Make a plot using a normal Python function as a callback
def func1(x):
	return 0.5*sin(x)+0.25*sin(2*x)+0.125*cos(4*x)

print "Making plot1.gif..."
# Make a widget and set callback
w = PlotWidget(500,500,-10,-2,10,2)
w.set_pymethod(func1)                     # Register our Python function
w.plot()
f = open("plot1.gif","w")
w.save(f)
f.close()

# Make a plot using an anonymous function

print "Making plot2.gif..."
w1 = PlotWidget(500,500,-4,-1,4,16)
w1.set_pymethod(lambda x: x*x)            # Register x^2 as a callback
w1.plot()
f = open("plot2.gif","w")
w1.save(f)
f.close()

# Make another plot using a built-in function

print "Making plot3.gif..."
w2 = PlotWidget(500,500,-7,-1.5,7,1.5)
w2.set_pymethod(sin)                      # Register sin(x) as a callback
w2.plot()
f = open("plot3.gif","w")
w2.save(f)
f.close()

The "plot" method for each widget is written entirely in C++ and assumes that it is calling a callback function written in C/C++. Little does it know that we have actually implemented this function in Python. With a little more work, we can even write a simple function plotting tool :

# Plot a function and spawn xv

import posix
import sys
import string
from plotwidget import *
from math import *

line = raw_input("Enter a function of x : ")
ranges = string.split(raw_input("Enter xmin,ymin,xmax,ymax :"),",")

print "Making a plot..."
w = PlotWidget(500,500,string.atof(ranges[0]),string.atof(ranges[1]),
               string.atof(ranges[2]),string.atof(ranges[3]))

# Turn user input into a Python function
code = "def func(x): return " + line
exec(code)

w.set_pymethod(func)
w.plot()
f = open("plot.gif","w")
w.save(f)
f.close()
posix.system("xv plot.gif &")

Other odds and ends

Adding native Python functions to a SWIG module

Sometimes it is desirable to add a native Python method to a SWIG wrapper file. Suppose you have the following Python/C function :

PyObject *spam_system(PyObject *self, PyObject *args) {
	char *command;
	int sts;
	if (!PyArg_ParseTuple(args,"s",&command))
		return NULL;
	sts = system(command);
	return Py_BuildValue("i",sts);
}

This function can be added to a SWIG module using the following declaration :

%native(system) spam_system;        // Create a command called `system'

Alternatively, you can use the full function declaration like this

%native(system) PyObject *spam_system(PyObject *self, PyObject *args);

or

%native(system) extern PyObject *spam_system(PyObject *self, PyObject *args);

The gory details of shadow classes

This section describes the process by which SWIG creates shadow classes and some of the more subtle aspects of using them.

A simple shadow class

Consider the following declaration from our previous example :

%module pde
struct Grid2d {
  Grid2d(int ni, int nj);
  ~Grid2d();
  double **data;
  int      xpoints;
  int      ypoints;
};

The SWIG generated class for this structure looks like the following :

# This file was created automatically by SWIG.
import pdec
class Grid2dPtr :
    def __init__(self,this):
        self.this = this
        self.thisown = 0
    def __del__(self):
        if self.thisown == 1 :
            pdec.delete_Grid2d(self.this)
    def __setattr__(self,name,value):
        if name == "data" :
            pdec.Grid2d_data_set(self.this,value)
            return
        if name == "xpoints" :
            pdec.Grid2d_xpoints_set(self.this,value)
            return
        if name == "ypoints" :
            pdec.Grid2d_ypoints_set(self.this,value)
            return
        self.__dict__[name] = value
    def __getattr__(self,name):
        if name == "data" : 
            return pdec.Grid2d_data_get(self.this)
        if name == "xpoints" : 
            return pdec.Grid2d_xpoints_get(self.this)
        if name == "ypoints" : 
            return pdec.Grid2d_ypoints_get(self.this)
        return self.__dict__[name]
    def __repr__(self):
        return "<C Grid2d instance>"
class Grid2d(Grid2dPtr):
    def __init__(self,arg0,arg1) :
        self.this = pdec.new_Grid2d(arg0,arg1)
        self.thisown = 1

Module names

Shadow classes are built using the low-level SWIG generated C interface. This interface is named "modulec" where "module" is the name of the module specified in a SWIG interface file. The Python code for the shadow classes is created in a file "module.py". This is the file that should be loaded when a user wants to use the module.

Two classes

For each structure or class found in an interface file, SWIG creates two Python classes. If a class is named "Grid2d", one of these classes will be named "Grid2dPtr" and the other named "Grid2d". The Grid2dPtr class is used to turn wrap a Python class around an already preexisting Grid2d pointer. For example :

>>> gptr = create_grid2d()         # Returns a Grid2d from somewhere
>>> g = Grid2dPtr(gptr)            # Turn it into a Python class
>>> g.xpoints
50
>>>

The Grid2d class, on the other hand, is used when you want to create a new Grid2d object from Python. In reality, it inherits all of the attributes of a Grid2dPtr, except that its constructor calls the corresponding C++ constructor to create a new object. Thus, in Python, this would look something like the following :

>>> g = Grid2d(50,50)           # Create a new Grid2d
>>> g.xpoints
50
>>>

This two class model is a tradeoff. In order to support C/C++ properly, it is necessary to be able to create Python objects from both pre-existing C++ objects and to create entirely new C++ objects in Python. While this might be accomplished using a single class, it would complicate the handling of constructors considerably. The two class model, on the other hand, works, is consistent, and is relatively easy to use. In practice, you probably won't even be aware that there are two classes working behind the scenes.

The this pointer

Within each shadow class, the member "this" contains the actual C/C++ pointer to the object. You can check this out yourself by typing something like this :

>>> g = Grid2d(50,50)
>>> print g.this
_1008fe8_Grid2d_p
>>>

Direct manipulation of the "this" pointer is generally discouraged. In fact forget that you read this.

Object ownership

Ownership is a critical issue when mixing C++ and Python. For example, suppose I create a new object in C++, but later use it to create a Python object. If that object is being used elsewhere in the C++ code, we clearly don't want Python to delete the C++ object when the Python object is deleted. Similarly, what if I create a new object in Python, but C++ saves a pointer to it and starts using it repeatedly. Clearly, we need some notion of who owns what. Since sorting out all of the possibilities is probably impossible, SWIG shadow classes always have an attribute "thisown" that indicates whether or not Python owns an object. Whenever an object is created in Python, Python will be given ownership by setting thisown to 1. When a Python class is created from a pre-existing C/C++ pointer, ownership is assumed to belong to the C/C++ code and thisown will be set to 0.

Ownership of an object can be changed as necessary by changing the value of thisown. When set, Python will call the C/C++ destructor when the object is deleted. If it is zero, Python will never call the C/C++ destructor.

Constructors and Destructors

C++ constructors and destructors will be mapped into Python's __init__ and __del__ methods respectively. Shadow classes always contain these methods even if no constructors or destructors were available in the SWIG interface file. The Python destructor will only call a C/C++ destructor if self.thisown is set.

Member data

Member data of an object is accessed through Python's __getattr__ and __setattr__ methods.

Printing

SWIG automatically creates a Python __repr__ method for each class. This forces the class to be relatively well-behaved when printing or being used interactively in the Python interpreter.

Shadow Functions

Suppose you have the following declarations in an interface file :

%module vector
struct Vector {
	Vector();
	~Vector();
	double x,y,z;
};

Vector addv(Vector a, Vector b);

By default, the function addv will operate on Vector pointers, not Python classes. However, the SWIG Python module is smart enough to know that Vector has been wrapped into a Python class so it will create the following replacement for the addv() function.

def addv(a,b):
	result = VectorPtr(vectorc.addv(a.this,b.this))
	result.thisown = 1
	return result

Function arguments are modified to use the "this" pointer of a Python Vector object. The result is a pointer to the result which has been allocated by malloc or new (this behavior is described in the chapter on SWIG basics), so we simply create a new VectorPtr with the return value. Since the result involved an implicit malloc, we set the ownership to 1 indicating that the result is to be owned by Python and that it should be deleted when the Python object is deleted. As a result, operations like this are perfectly legal and result in no memory leaks :

>>> v = add(add(add(add(a,b),c),d),e)

Substitution of complex datatypes occurs for all functions and member functions involving structure or class definitions. It is rarely necessary to use the low-level C interface when working with shadow classes.

Nested objects

SWIG shadow classes support nesting of complex objects. For example, suppose you had the following interface file :

%module particle

typedef struct {
  Vector();
  double x,y,z;
} Vector;

typedef struct {
  Particle();
 ~Particle();
  Vector r;
  Vector v;
  Vector f;
  int    type;
} Particle;

In this case you will be able to access members as follows :

>>> p = Particle()
>>> p.r.x = 0.0
>>> p.r.y = -1.5
>>> p.r.z = 2.0
>>> p.v = addv(v1,v2)
>>> ...

Nesting of objects is implemented using Python's __setattr__ and __getattr__ functions. In this case, they would look like this :

class ParticlePtr:
	...
	def __getattr__(self,name):
		if name == "r":
			return particlec.VectorPtr(Particle_r_get(self.this))
		elif name == "v":
			return particlec.VectorPtr(Particle_v_get(self.this))
		...
	
	def __setattr__(self,name,value):
		if name == "r":
			particlec.Particle_r_set(self.this,value.this)
		elif name == "v":
			particlec.Particle_v_set(self.this,value.this)
		...

The attributes of any given object are only converted into a Python object when referenced. This approach is more memory efficient, faster if you have a large collection of objects that aren't examined very often, and works with recursive structure definitions such as :

struct Node {
	char *name;
	struct Node *next;
};

Nested structures such as the following are also supported by SWIG. These types of structures tend to arise frequently in database and information processing applications.

typedef struct {  
	unsigned int dataType;
	union {
		int       intval;
		double    doubleval;
		char     *charval;
		void     *ptrvalue;
		long      longval;
		struct {
			int    i;
			double f;
			void   *v;
			char name[32];
              } v;
	} u;
} ValueStruct;

Access is provided in an entirely natural manner,

>>> v = new_ValueStruct()       # Create a ValueStruct somehow
>>> v.dataType
1
>>> v.u.intval
45
>>> v.u.longval
45
>>> v.u.v.v = _0_void_p
>>>

To support the embedded structure definitions, SWIG has to extract the internal structure definitions and use them to create new Python classes. In this example, the following shadow classes are created :

# Class corresponding to union u member
class ValueStruct_u :
	...
# Class corresponding to struct v member of union u
class ValueStruct_u_v :
	...

The names of the new classes are formed by appending the member names of each embedded structure.

Inheritance and shadow classes

Since shadow classes are implemented in Python, you can use any of the automatically generated classes as a base class for more Python classes. However, you need to be extremely careful when using multiple inheritance. When multiple inheritance is used, at most ONE SWIG generated shadow class can be involved. If multiple SWIG generated classes are used in a multiple inheritance hierarchy, you will get name clashes on the this pointer, the __getattr__ and __setattr__ functions won't work properly and the whole thing will probably crash and burn. Perhaps it's best to think of multiple inheritance as a big hammer that can be used to solve alot of problems, but it hurts quite alot if you accidently drop it on your foot....

Methods that return new objects

By default SWIG assumes that constructors are the only functions returning new objects to Python. However, you may have other functions that return new objects as well. For example :

Vector *cross_product(Vector *v1, Vector *v2) {
	Vector *result = new Vector();
	result = ... compute cross product ...
	return result;
}

When the value is returned to Python, we want Python to assume ownership. The brute force way to do this is to simply change the value of thisown. For example :

>>> v = cross_product(a,b)
>>> v.thisown = 1                        # Now Python owns it

Unfortunately, this is ugly and it doesn't work if we use the result as a temporary value :

w = vector_add(cross_product(a,b),c)     # Results in a memory leak

However, you can provide a hint to SWIG when working with such a function as shown :

// C Function returning a new object
%new Vector *cross_product(Vector *v1, Vector *v2);

The %new directive only provides a hint that the function is returning a new object. The Python module will assign proper ownership of the object when this is used.

Performance concerns and hints

Shadow classing is primarily intended to be a convenient way of accessing C/C++ objects from Python. However, if you're directly manipulating huge arrays of complex objects from Python, performance may suffer greatly. In these cases, you should consider implementing the functions in C or thinking of ways to optimize the problem.

There are a number of ways to optimize programs that use shadow classes. Consider the following two code fragments involving the Particle data structure in a previous example :

def force1(p1,p2):
	dx = p2.r.x - p1.r.x
	dy = p2.r.y - p1.r.y
	dz = p2.r.z - p1.r.z
	r2 = dx*dx + dy*dy + dz*dz
	f = 1.0/(r2*math.sqrt(r2))
	p1.f.x = p1.f.x + f*dx
	p2.f.x = p2.f.x - f*dx
	p1.f.y = p1.f.y + f*dy
	p2.f.y = p2.f.y - f*dy
	p1.f.z = p1.f.z + f*dz
	p2.f.z = p2.f.z - f*dz

def force2(p1,p2):
	r1 = p1.r
	r2 = p2.r
	dx = r2.x - r1.x
	dy = r2.y - r1.y
	dz = r2.z - r1.z
	r2 = dx*dx + dy*dy + dz*dz
	f = 1.0/(r2*math.sqrt(r2))
	f1 = p1.f
	f2 = p2.f
	f1.x = f1.x + f*dx
	f2.x = f2.x - f*dx
	f1.y = f1.y + f*dy
	f2.y = f2.y - f*dy
	f1.z = f1.z + f*dz
	f2.z = f2.z - f*dz

The first calculation simply works with each Particle structure directly. Unfortunately, it performs alot of dereferencing of objects. If the calculation is restructured to use temporary variables as shown in force2, it will run significantly faster--in fact, on my machine, the second code fragment runs more than twice as fast as the first one.

If performance is even more critical you can use the low-level C interface which eliminates all of the overhead of going through Python's class mechanism (at the expense of coding simplicity). When Python shadow classes are used, the low level C interface can still be used by importing the `modulec' module where `module' is the name of the module you used in the SWIG interface file.


SWIG 1.1 - Last Modified : Mon Aug 4 10:47:07 1997