|
|
|
 |
|
Illustration by Lia Avant
|
 |
| Extending Python with C: Part 1 |
Extending Python with C: Part 1
- - - - - - - - - - - -
By Alex Martelli | Originally published in Py Issue 1
If you know C, it's not too hard to use it to code Python extension modules. Let's examine a simple example. Make a new directory tinydir, and, in it, write with any text editor a file tiny.c:
1 #include /* get all declarations/macros */
2
3 static PyObject*
4 tiny(PyObject* self, PyObject* args) /* proto for all exposed funcs */
5 {
6 if(!PyArg_ParseTuple(args, "")) /* receive arguments (no args) */
7 return 0; /* propagate error if any */
8 return Py_BuildValue("s","it works!"); /* build and return result */
9 }
10
11 static PyMethodDef tinyMethods[] = { /* methods exposed from module */
12 {"tiny", tiny, METH_VARARGS, "A tiny function"}, /* descriptor */
13 {0} /* sentinel */
14 };
15
16 void
17 inittiny() /* called by Python on import */
18 {
19 Py_InitModule("tiny", tinyMethods); /* init module with methods */
20 }
The line numbers in the listing are not part of the file – I just use them to help me explain the file's contents, line by line.
 |
| |
| |
| Sidebar - Building with distutils |
|
If your platform is Windows, building with the distutils works "out of the box" only if you have Microsoft's Visual C++ 6 compiler installed. There are ways to build extensions with free compilers, such as:
- the compiler inside Microsoft's ".NET Development Platform"
- Borland's free C/C++ compiler
- the mingw32 version of gcc
- the cygwin version of gcc (if you use Python in cygwin) but this article does not cover these specific details.
On other platforms, such as Linux and OpenBSD, both Python and its extensions are implicitly built with "the C compiler that comes with the platform", typically gcc, so that the issue of using other compilers arises even more rarely than on Windows.
|
|
|
| |
 |
Let's examine and explain this short C source file in full detail, starting from the end. Since the module is called tiny, when the module is installed you can load it into Python with the statement import tiny.
To execute import tiny, Python finds the tiny extension module (normally called tiny.pyd on Windows, tiny.so on Linux) in some directory along the sys.path, loads the module into memory, and finds and calls a function named inittiny in the module.
The name of a module's initialization function is always init followed by the name of the module. Lines 16-20 define the initialization function. All inittiny has to do, in this simple example, is to initialize the Python module object, which it does on line 19 by calling function Py_InitModule (part of the Python C API, whose declarations are brought in by the #include on line 1).
Py_InitModule's first argument is the name of the module it must initialize. The second argument is an array of PyMethodDef structures -- here, the tryMethods array defined in lines 11-14. The array is always terminated by a "sentinel" structure with all fields at 0 (here defined on line 13). Before the sentinel, the array can have any number of PyMethodDef structures, each describing a C function that the module exposes to Python; here, we only have one such descriptor (on line 12), because module tiny exposes only one function to Python. The descriptor structure has four fields:
- the function's name as seen from Python (a string)
- the function's name as coded in C
- a code which is always
METH_VARARGS (unless you want to get into advanced issues such as functions which Python code can call with named-arguments; this article does not cover such advanced issues, but only the basics)
- the docstring for the function.
The second field names a C function that must have exactly the signature used here in lines 3-4 to define function tiny: the function should be static, must return a PyObject*, and must accept two PyObject* arguments. All Python objects are always passed to C, and returned from C, as pointers to struct PyObject, the internal Python struct which describes the Python object in detail. The first argument will always be 0, the null pointer (except for advanced issues such as using C coded functions as object-methods rather than as functions in a module). The second argument will always be the Python tuple of the arguments that Python code has used to call the function.
In line 6, the function examines its arguments, by calling function PyArg_ParseTuple (another function from the Python C API). The first argument to function PyArg_ParseTuple is the PyObject* that function tiny received as its second argument; the second argument to PyArg_ParseTuple is a string describing the types of arguments that tiny expects – in this case, an empty string, which means that tiny expects no arguments at all. Function PyArg_ParseTuple returns 0 if it detects any error; in this case, function tiny also returns 0, at line 7, and the calling Python code eventually receives a Python exception describing the error. In line 8, function tiny returns its result to Python. tiny builds this result by calling Py_BuildValue, yet another function from the Python C API. The first argument to function Py_BuildValue is a string describing the type of Python object that Py_BuildValue must build – in this case, "s", meaning the result is a Python str object. The second argument to Py_BuildValue is the C-type "string" (array of char with a terminating 0) whose value Py_BuildValue must copy into the resulting Python str object.
To build your extension module, use your favorite text editor to build in directory tinydir a file setup.py, using Python's standard distutils package:
1 from distutils.core import setup, Extension
2
3 setup(name = "tiny",
4 version = "1.0",
5 description = "A tiny example extension",
6 maintainer = "Alex Martelli",
7 maintainer_email = "aleaxit@yahoo.com",
8
9 ext_modules = [ Extension('tiny', sources=['tiny.c']) ]
10 )
Now, assuming you have write permission for your Python installation's site-packages directory, all you need to do is (from a shell prompt, or equivalently a CMD.EXE or COMMAND.COM prompt in Windows) to make tinydir your current directory (with cd tinydir), then run:
python setup.py install
This invokes your C compiler with whatever options are appropriate on your platform for building Python extensions, creates the extension module (tiny.so or tiny.pyd, depending on the platform), and copies the module to your site-packages directory. In other words, after this command has finished you can try out your new module, for example from a Python interactive session:
>>> import tiny
>>> dir(tiny)
['__doc__', '__file__', '__name__', 'tiny']
>>> print tiny.tiny.__doc__
Truly tiny function
>>> print tiny.tiny()
it works!
>>> print tiny.tiny(23)
Traceback (most recent call last):
File "", line 1, in ?
TypeError: function takes exactly 0 arguments (1 given)
As you see, besides trying the base-case functionality, we are also checking that the docstring of function tiny is correct, and also that the appropriate exception is raised when by mistake tiny is called with arguments.
Of course, this truly tiny extension module doesn't do very much. However, it's a skeleton, on which we can attach muscles and sinews in whatever ways we need. Most of the details you need to make your C coded Python extension perform basic tasks can be found online, as sub-urls of http://www.python.org/doc/current/ or as the same sub-urls of your own installation of the Python documentation; for example: ext/parseTuple.html tells you all about PyArg_ParseTuple, ext/buildValue.html tells you all about Py_BuildValue. For example, it's easy to add to our tiny module a function receiving two integers and returning their integer sum. Edit tiny.c to add the following function isum:
static PyObject*
isum(PyObject* self, PyObject* args)
{
int a, b;
if(!PyArg_ParseTuple(args, "ii", &a, &b))
return 0;
return Py_BuildValue("i", a+b);
}
and also add a line inside tinyMethods:
{"isum", isum, METH_VARARGS, "Sum two integers"},
then, rerun python setup.py install, and try:
>>> import tiny # you need reload(tiny) if tiny is already loaded
>>> dir(tiny)
['__doc__', '__file__', '__name__', 'isum', 'tiny']
>>> tiny.isum(100, 23)
123
>>> tiny.isum(4.56, 7.89)
11
Note from this example that isum's arguments are truncated to integers, due to the "ii" descriptor string passed as the second argument to PyArg_ParseTuple. More often, we'll want to receive arguments as generic Python objects, PyObject* from C's viewpoint, and operate on them with the generic functions described at sub-urls api/abstract.html, api/object.html, api/number.html and so on.
To add to our tiny module a function receiving two objects of any type and returning their sum, edit tiny.c and add the function:
static PyObject*
sum(PyObject* self, PyObject* args)
{
PyObject *a, *b;
if(!PyArg_ParseTuple(args, "OO", &a, &b))
return 0;
return PyNumber_Add(a, b);
}
and also add a line inside tinyMethods:
{"sum", sum, METH_VARARGS, "Sum two objects"},
then, rerun python setup.py install, and try:
# you need reload(tiny) if tiny is already loaded
>>> import tiny
>>> dir(tiny)
['__doc__', '__file__', '__name__', 'isum', 'sum', 'tiny']
>>> print tiny.sum.__doc__
Sum two objects
>>> print tiny.sum(4.56, 7.89)
12.45
>>> print tiny.sum('be', 'bop')
bebop
Note from this example that, despite its name, PyNumber_Add is not just for numbers: like Python's + and operator.add, it works just as well to concatenate two sequences, such as strings.
Working with the "abstract object protocol", i.e., generic PyObject* objects and the functions that handle them, is often preferable to type-checking and working with the various "concrete object protocols", except of course when you must build an object of some specific, concrete type. Sometimes going down to concrete objects may offer a speed advantage, but be sure to profile your program and extensions carefully, both to confirm that you really need the acceleration, and also to verify that you are truly getting it – often you will find that the hoped-for advantage doesn't materialize.
This particular article is Copyright © 2002 Alex Martelli. All Rights Reserved.
|
Alex Martelli
lives in Italy and is a System Developer for AB Strakt, Sweden. He co-edited the "Python Cookbook" and is writing the forthcoming book "Python in a Nutshell."
|
|
 |
|