extend python with c extension modules
sometimes we need to call c function from python; one way to do this is to write a c extension module wrapping the c function; this requires a few lines of code; but once we understand the basic steps, this is straightforward; because this is c extension module, it only works with cpython;
the concept
the python api defines a set of functions, macros and variables that provide access to most aspects of the python run-time system; we can use the python api to unwrap python objects into c values, call c function with these c values, then wrap the result c value in a python object and return it;
to use the python api, include header file Python.h
;
the c function
for brevity, we use this example c function:
static int add(int a, int b)
{
return a + b;
}
the c extension module
for brevity, we name this c extension module foo
and its filename
foomodule.c
; now we start writing this module:
-
include python api header file:
#include <Python.h>
this header file defines functions, macros, variables etc. whose names start with
Py
orPY
; -
put the c function we want to call in this module:
static int add(int a, int b) { return a + b; }
we use
static
to hide its name from global; this is not a requirement, but makes things clean; in fact, as we shall see, all functions in this file, except the init function, arestatic
; again, this is not a requirement; -
write a python method wrapping this c function:
static PyObject * foo_add(PyObject *self, PyObject *args) { int a, b; if (!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL; int c = add(a, b); return PyLong_FromLong(c); }
this method parses arguments to get their c values, then call the c function on them, finally return the result as a python value;
-
now we have got the method, but still need some boilerplates to finish the module; specifically, we need to put this method in a method table, then define the module using a data structure which contains this method table:
static PyMethodDef FooMethods[] = { { "add", foo_add, METH_VARARGS, "Add two numbers." }, { NULL, NULL, 0, NULL } };
here, this method table has only 1 entry, plus a sentinel; the entry fields are: method name, c function for this method, calling convention for the c function, method docstring;
static struct PyModuleDef FooModule = { PyModuleDef_HEAD_INIT, "foo", "foo doc", -1, FooMethods };
the module definition structure includes a head, module name, module docstring, per-interpreter state size, method table;
-
we are almost done, but there is a final job: write an init function for this module:
PyMODINIT_FUNC PyInit_foo(void) { return PyModule_Create(&FooModule); }
this is the entrance to this module and must not be
static
; it creates a module object using the module definition structure;
put these snippets together to get the entire module file foomodule.c
:
#include <Python.h>
// c function: `add`
static int add(int a, int b)
{
return a + b;
}
// python method: `foo.add`
static PyObject *
foo_add(PyObject *self, PyObject *args)
{
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
return NULL;
int c = add(a, b);
return PyLong_FromLong(c);
}
// method table
static PyMethodDef FooMethods[] = {
// python method: `foo.add`
{ "add", foo_add, METH_VARARGS, "Add two numbers." },
// sentinel
{ NULL, NULL, 0, NULL }
};
// module definition structure
static struct PyModuleDef FooModule = {
PyModuleDef_HEAD_INIT,
// module name
"foo",
// module docstring
"foo doc",
// size of per-interpreter state: -1 if the
// module keeps state in global variables
-1,
// method table
FooMethods
};
// module initialization function
PyMODINIT_FUNC
PyInit_foo(void)
{
return PyModule_Create(&FooModule);
}
build the module
we can build the extension module using setuptools
, saving great time dealing
with compilers and linkers by ourselves; to do so, we need to write a setup.py
file like this:
from setuptools import Extension
from setuptools import setup
setup(
name='foo',
ext_modules=[Extension('foo',['foomodule.c'])],
)
then run this command:
python3 setup.py build_ext
this calls compiler and linker to build the extension module into a dynamically
linked library (dll, .so
on linux); this dll lives in the build
dir; to use
this dll, make sure its path can be founed by the python interpreter, then just
import it using its name (foo
):
import foo
print(foo.add(3, 4))
output:
7