So far in this
chapter, we’ve been dealing with C extension modules—flat
function libraries. To implement multiple-instance objects in C, you need
to code a
C extension type
, not a module. Like Python
classes, C types generate multiple-instance objects and can overload
(i.e., intercept and implement) Python expression operators and type
operations. C types can also support subclassing just like Python classes,
largely because the type/class distinction has largely evaporated in
Python 3.X.
You can see what C types look like in Python’s own source library
tree; look for the
Objects
directory
there. The code required for a C type can be large—it defines instance
creation, named methods, operator implementations, an iterator type, and
so on, and links all these together with tables—but is largely boilerplate
code that is structurally the same for most types.
You can code new object types in C manually like this, and in some
applications, this approach may make sense. But you don’t necessarily have
to—because SWIG knows how to generate glue code for
C++
classes
, you can instead
automatically
generate all the C extension and wrapper class code required to integrate
such an object, simply by running SWIG over an appropriate class
declaration. The wrapped C++ class provides a multiple-instance datatype
much like the C extension type, but it can be substantially simpler for
you to code because SWIG handles language integration details.
Here’s how—given a C++ class declaration and special command-line
settings, SWIG generates the following:
A C++-coded Python extension module with accessor functions that
interface with the C++ class’s methods and members
A Python-coded module with a wrapper class (called a “shadow” or
“proxy” class in SWIG-speak) that interfaces with the C++ class
accessor functions module
As we did earlier, to use SWIG in this domain, write and debug your
class as though it would be used only from C++. Then, simply run SWIG in
your makefile to scan the
C++
class
declaration and compile and link its output. The end result is that by
importing the shadow class in your Python scripts, you can utilize C++
classes as though they were really coded in Python. Not only can Python
programs make and use instances of the C++ class, they can also customize
it by subclassing the generated shadow class.
To see how this
works, we need a C++ class. To illustrate, let’s code one
to be used in Python scripts. You have to understand C++ to make sense
of this section, of course, and SWIG supports advanced C++ tools
(including templates and overloaded functions and operators), but I’ll
keep this example simple for illustration. The following C++ files
define aNumber
class with four
methods (add
,sub
,square
, anddisplay
), a data member (data
), and a constructor and destructor.
Example 20-14
shows the header
file.
Example 20-14. PP4E\Integrate\Extend\Swig\Shadow\number.h
class Number
{
public:
Number(int start); // constructor
~Number(); // destructor
void add(int value); // update data member
void sub(int value);
int square(); // return a value
void display(); // print data member
int data;
};
Example 20-15
is the C++
class’s implementation file; most methods print a message when called to
trace class operations. Notice how this usesprintf
instead of C++’scout
; this once resolved an output overlap
issue when mixing C++cout
with
Python 2.X standard output streams on Cygwin. It’s probably a moot point
today—because Python 3.X’s output system and buffering might mix with
C++’s arbitrarily, C++ should generally flush the output stream (withfflush(stdout)
orcout<
) if it prints intermixed
text that doesn’t end in a newline. Obscure but true when disparate
language systems are mixed.
Example 20-15. PP4E\Integrate\Extend\Swig\Shadow\number.cxx
///////////////////////////////////////////////////////////////
// implement a C++ class, to be used from Python code or not;
// caveat: cout and print usually both work, but I ran into a
// c++/py output overlap issue on Cygwin that prompted printf
///////////////////////////////////////////////////////////////
#include "number.h"
#include "stdio.h" // versus #include "iostream.h"
Number::Number(int start) {
data = start; // python print goes to stdout
printf("Number: %d\n", data); // or: cout << "Number: " << data << endl;
}
Number::~Number() {
printf("~Number: %d\n", data);
}
void Number::add(int value) {
data += value;
printf("add %d\n", value);
}
void Number::sub(int value) {
data -= value;
printf("sub %d\n", value);
}
int Number::square() {
return data * data; // if print label, fflush(stdout) or cout << flush
}
void Number::display() {
printf("Number=%d\n", data);
}
So that you can compare languages, the following is how this class
is used in a C++ program.
Example 20-16
makes aNumber
object, calls its methods, and fetches
and sets its data attribute directly (C++ distinguishes between
“members” and “methods,” while they’re usually both called “attributes”
in Python).
Example 20-16. PP4E\Integrate\Extend\Swig\Shadow\main.cxx
#include "iostream.h"
#include "number.h"
main()
{
Number *num;
int res, val;
num = new Number(1); // make a C++ class instance
num->add(4); // call its methods
num->display();
num->sub(2);
num->display();
res = num->square(); // method return value
cout << "square: " << res << endl;
num->data = 99; // set C++ data member
val = num->data; // fetch C++ data member
cout << "data: " << val << endl;
cout << "data+1: " << val + 1 << endl;
num->display();
cout << num << endl; // print raw instance ptr
delete num; // run destructor
}
You can use theg++
command-line C++ compiler program to compile and run this code
on
Cygwin (it’s the same on Linux). If you don’t use a
similar system, you’ll have to extrapolate; there are far too many C++
compiler differences to list here. Type the compile command directly or
use thecxxtest
target in this
example directory’s makefile shown ahead, and then run the purely C++
program
created:
.../PP4E/Integrate/Extend/Swig/Shadow$make -f makefile.number-swig cxxtest
g++ main.cxx number.cxx -Wno-deprecated
.../PP4E/Integrate/Extend/Swig/Shadow$./a.exe
Number: 1
add 4
Number=5
sub 2
Number=3
square: 9
data: 99
data+1: 100
Number=99
0xe502c0
~Number: 99
But enough C++: let’s get back to Python. To use the C++Number
class of the preceding section in
Python scripts, you need to code or generate a glue logic layer between
the two languages, just as in prior C extension examples. To generate
that layer automatically, write a SWIG input file like the one shown in
Example 20-17
.
Example 20-17. PP4E\Integrate\Extend\Swig\Shadow\number.i
/********************************************************
* Swig module description file for wrapping a C++ class.
* Generate by running "swig -c++ -python number.i".
* The C++ module is generated in file number_wrap.cxx;
* module 'number' refers to the number.py shadow class.
********************************************************/
%module number
%{
#include "number.h"
%}
%include number.h
This interface file simply directs SWIG to read the C++ class’s
type signature information from the %-included
number.h
header file. SWIG uses the class
declaration to generate two different Python modules again:
number_wrap.cxx
A C++ extension module with class accessor functions
number.py
A Python shadow class module that wraps accessor
functions
The former must be compiled into a binary library. The latter
imports and uses the former’s compiled form and is the file that Python
scripts ultimately import. As for simple functions, SWIG achieves the
integration with a combination of Python and
C++
code.
After running SWIG, the Cygwin makefile shown in
Example 20-18
combines the generated
number_wrap.cxx
C++ wrapper code
module with the C++ class implementation file to create a
_number.dll
—a dynamically loaded extension
module that must be in a directory on your Python module search path
when imported from a Python script, along with the generated
number.py
(all files are in the same current
working directory here).
As before, the compiled C extension module must be named with a
leading underscore in SWIG today:
_number.dll
, following a Python convention,
rather than the other formats used by earlier releases. The shadow class
module
number.py
internally imports
_number.dll
. Be sure to use a-c++
command-line argument for SWIG;
an older-shadow
argument is no longer needed to create the wrapper class in addition to
the lower-level functional interface module, as this is enabled by
default.
Example 20-18. PP4E\Integrate\Extend\Swig\Shadow\makefile.number-swig
###########################################################################
# Use SWIG to integrate the number.h C++ class for use in Python programs.
# Update: name "_number.dll" matters, because shadow class imports _number.
# Update: the "-shadow" swig command line arg is deprecated (on by default).
# Update: swig no longer creates a .doc file to rm here (ancient history).
###########################################################################
PYLIB = /usr/local/bin
PYINC = /usr/local/include/python3.1
SWIG = /cygdrive/c/temp/swigwin-2.0.0/swig
all: _number.dll number.py
# wrapper + real class
_number.dll: number_wrap.o number.o
g++ -shared number_wrap.o number.o -L$(PYLIB) -lpython3.1 -o [email protected]
# generated class wrapper module(s)
number_wrap.o: number_wrap.cxx number.h
g++ number_wrap.cxx -c -g -I$(PYINC)
number_wrap.cxx: number.i
$(SWIG) -c++ -python number.i
number.py: number.i
$(SWIG) -c++ -python number.i
# wrapped C++ class code
number.o: number.cxx number.h
g++ number.cxx -c -g -Wno-deprecated
# non Python test
cxxtest:
g++ main.cxx number.cxx -Wno-deprecated
clean:
rm -f *.pyc *.o *.dll core a.exe
force:
rm -f *.pyc *.o *.dll core a.exe number_wrap.cxx number.py
As usual, run this makefile to generate and compile the necessary
glue code into an extension module that can be imported by Python
programs:
.../PP4E/Integrate/Extend/Swig/Shadow$make -f makefile.number-swig
/cygdrive/c/temp/swigwin-2.0.0/swig -c++ -python number.i
g++ number_wrap.cxx -c -g -I/usr/local/include/python3.1
g++ number.cxx -c -g -Wno-deprecated
g++ -shared number_wrap.o number.o -L/usr/local/bin -lpython3.1 -o _number.dll
.../PP4E/Integrate/Extend/Swig/Shadow$ls
_number.dll makefile.number-swig number.i number_wrap.cxx
a.exe number.cxx number.o number_wrap.o
main.cxx number.h number.py
Once the glue code is generated and compiled, Python scripts can
access the C++ class as though it were coded in Python. In fact, it
is—the imported
number.py
shadow
class which runs on top of the extension module is generated Python
code.
Example 20-19
repeats the
main.cxx
file’s class tests. Here,
though, the
C++
class is being utilized from the
Python programming language—an arguably amazing feat, but the code is
remarkably natural on the Python side of the fence.
Example 20-19. PP4E\Integrate\Extend\Swig\Shadow\main.py
"""
use C++ class in Python code (c++ module + py shadow class)
this script runs the same tests as the main.cxx C++ file
"""
from number import Number # imports .py C++ shadow class module
num = Number(1) # make a C++ class object in Python
num.add(4) # call its methods from Python
num.display() # num saves the C++ 'this' pointer
num.sub(2)
num.display()
res = num.square() # converted C++ int return value
print('square: ', res)
num.data = 99 # set C++ data member, generated __setattr__
val = num.data # get C++ data member, generated __getattr__
print('data: ', val) # returns a normal Python integer object
print('data+1: ', val + 1)
num.display()
print(num) # runs repr in shadow/proxy class
del num # runs C++ destructor automatically
Because the C++ class and its wrappers are automatically loaded
when imported by the
number.py
shadow class module, you run this script like any other:
.../PP4E/Integrate/Extend/Swig/Shadow$python main.py
Number: 1
add 4
Number=5
sub 2
Number=3
square: 9
data: 99
data+1: 100
Number=99>
~Number: 99
Much of this output is coming from the C++ class’s methods and is
largely the same as the
main.cxx
results shown in
Example 20-16
(less the
instance output format—it’s a Python shadow class instance now).
SWIG implements integrations as a C++/Python combination, but
you can always use the generated accessor functions module if you want
to, as in
Example 20-20
. This
version runs the C++ extension module directly without the shadow
class, to demonstrate how the shadow class maps calls back to
C++.
Example 20-20. PP4E\Integrate\Extend\Swig\Shadow\main_low.py
"""
run similar tests to main.cxx and main.py
but use low-level C accessor function interface
"""
from _number import * # c++ extension module wrapper
num = new_Number(1)
Number_add(num, 4) # pass C++ 'this' pointer explicitly
Number_display(num) # use accessor functions in the C module
Number_sub(num, 2)
Number_display(num)
print(Number_square(num))
Number_data_set(num, 99)
print(Number_data_get(num))
Number_display(num)
print(num)
delete_Number(num)
This script generates essentially the same output as
main.py
, but it’s been slightly simplified, and
the C++ class instance is something lower level than the proxy class
here:
.../PP4E/Integrate/Extend/Swig/Shadow$python main_low.py
Number: 1
add 4
Number=5
sub 2
Number=3
9
99
Number=99
_6025aa00_p_Number
~Number: 99
Using the
extension module directly works, but there is no obvious
advantage to moving from the shadow class to functions here. By using
the shadow class, you get both an object-based interface to C++ and a
customizable Python object. For instance, the Python module shown in
Example 20-21
extends the C++
class, adding an extraprint
call
statement to the C++add
method and
defining a brand-newmul
method.
Because the shadow class is pure Python, this works naturally.
Example 20-21. PP4E\Integrate\Extend\Swig\Shadow\main_subclass.py
"sublass C++ class in Python (generated shadow class)"
from number import Number # import shadow class
class MyNumber(Number):
def add(self, other): # extend method
print('in Python add...')
Number.add(self, other)
def mul(self, other): # add new method
print('in Python mul...')
self.data = self.data * other
num = MyNumber(1) # same tests as main.cxx, main.py
num.add(4) # using Python subclass of shadow class
num.display() # add() is specialized in Python
num.sub(2)
num.display()
print(num.square())
num.data = 99
print(num.data)
num.display()
num.mul(2) # mul() is implemented in Python
num.display()
print(num) # repr from shadow superclass
del num
Now we get extra messages out ofadd
calls, andmul
changes the C++ class’s data member
automatically when it assignsself.data
—the Python code extends the C++
code:
.../PP4E/Integrate/Extend/Swig/Shadow$python main_subclass.py
Number: 1
in Python add...
add 4
Number=5
sub 2
Number=3
9
99
Number=99
in Python mul...
Number=198
<__main__.MyNumber; proxy of>
~Number: 198
In other words, SWIG makes it easy to use C++ class libraries as
base classes in your Python scripts. Among other things, this allows
us to leverage existing C++ class libraries in Python scripts and
optimize by coding parts of class hierarchies in C++ when needed. We
can do much the same with C extension types today since types are
classes (and vice versa), but wrapping C++ classes with SWIG is often
much simpler.
As usual, you can import the C++ class interactively to
experiment with it some
more—
besides demonstrating a few more
salient properties here, this technique allows us to test wrapped C++
classes at the Python interactive prompt:
.../PP4E/Integrate/Extend/Swig/Shadow$python
>>>import _number
>>>_number.__file__
# the C++ class plus generated glue module
'_number.dll'
>>>import number
# the generated Python shadow class module
>>>number.__file__
'number.py'
>>>x = number.Number(2)
# make a C++ class instance in Python
Number: 2
>>>y = number.Number(4)
# make another C++ object
Number: 4
>>>x, y
(>, >)
>>>x.display()
# call C++ method (like C++ x->display())
Number=2
>>>x.add(y.data)
# fetch C++ data member, call C++ method
add 4
>>>x.display()
Number=6
>>>y.data = x.data + y.data + 32
# set C++ data member
>>>y.display()
# y records the C++ this pointer
Number=42
>>>y.square()
# method with return value
1764
>>>t = y.square()
>>>t, type(t)
# type is class in Python 3.X
(1764,)
Naturally, this example uses a small C++ class to underscore the
basics, but even at this level, the seamlessness of the Python-to-C++
integration we get from SWIG is astonishing. Python code uses C++
members and methods as though they are Python code. Moreover, this
integration transparency still applies once we step up to more
realistic C++ class libraries.
So what’s the catch? Nothing much, really, but if you start
using SWIG in earnest, the biggest downside may be that SWIG cannot
handle every feature of C++ today. If your classes use some esoteric
C++ tools (and there are many), you may need to handcode simplified
class type declarations for SWIG instead of running SWIG over the
original class header files. SWIG development is ongoing, so you
should consult the SWIG manuals and website for more details on these
and other topics.
In return for any such trade-offs, though, SWIG can completely
obviate the need to code glue layers to access C and C++ libraries
from Python scripts. If you have ever coded such layers by hand in the
past, you already know that this can be a
very
big win.
If you do go the handcoded route, though, consult Python’s
standard extension manuals for more details on both API calls used in
this chapter, as well as additional extension tools we don’t have
space to cover in this text. C extensions can run the gamut from short
SWIG input files to code that is staunchly wedded to the internals of
the Python interpreter; as a rule of thumb, the former survives the
ravages of time much better than the
latter.