[关闭]
@kailaix 2016-08-31T04:45:05.000000Z 字数 4747 阅读 1950

How to write C extension with Python.h

In this short article, I'd like to introduce the minimal effort to write C extension for Python.

Why do we have to write C extension for Python? In most times, Python does well on the calculations we want to make. However, sometimes we'd like to speed up our program by improving the most time-consuming and CPU-consuming parts. Switching to C programming might be a remedy but coding in pure C will be miserable. In such a situation, writing C extension is definitely a good idea.

There are might ways to write C extension. For example, swig, Cython and so on are great tools for such a purpose. But we will focus on the primitive "Python.h way" in this article to achieve the goal.

The Problem

We will introduce the method by solving the following problem:

Consider the following differential equation:


with initial condition . Calculate and .

The answer is obvious as we can solve the problem analyticall: . But to illustrate, we will use backward Euler's method to solve it numerically. The method reads:

that is, the step size is . Let , then ; also when , .

The Struction of C Source Code

Like MATLAB mex function, Python.h provides us with power APIs to write C extension. In a typical C source code to be integrated into Python program, there are four parts:

C functions

The first part consists of primitive C functions like

  1. double _value_at_one(int n){
  2. double dt = 1.0/n;
  3. double f = 1;
  4. for(int i=0;i<n;i++)
  5. {
  6. f = 1.0/(1-dt)*f;
  7. }
  8. return f;
  9. }
  10. double _value_at_two(int n){
  11. double dt = 2.0/n;
  12. double f = 1;
  13. for(int i=0;i<n;i++)
  14. {
  15. f = 1.0/(1-dt)*f;
  16. }
  17. return f;
  18. }

This is pure C and we do not have to worry about interations between C and Python.

converter
  1. static PyObject *
  2. value_at_one(PyObject *self, PyObject *args)
  3. {
  4. int n;
  5. if (!PyArg_ParseTuple(args,"i",&n)){
  6. return NULL;
  7. }
  8. return Py_BuildValue("d",_value_at_one(n));
  9. }
  10. static PyObject *
  11. value_at_two(PyObject *self, PyObject *args)
  12. {
  13. int n;
  14. if (!PyArg_ParseTuple(args,"i",&n)){
  15. return NULL;
  16. }
  17. return Py_BuildValue("d",_value_at_two(n));
  18. }

In this part, we wrap the C functions in a "converter". There are three kinds of converters, which differs only in the arguments they take: besides self arguments, functions with no arguments,functions with tuple arguments and functions with key word arguments.

For the second case, the standard API to parse the arguments is PyArg_ParseTuple function.

Py_BuildValue constructs a Python data structure from C data structure.

mapping table

Mapping table is a array of structures that assemble all the functions in a single container.

  1. static PyMethodDef calculate_methods[] = {
  2. /* The cast of the function is necessary since PyCFunction values
  3. * only take two PyObject* parameters, and keywdarg_parrot() takes
  4. * three.
  5. */
  6. {"value_at_one", (PyCFunction)value_at_one, METH_VARARGS,
  7. "This is a demo"},
  8. {"value_at_two", (PyCFunction)value_at_two, METH_VARARGS,
  9. "value_at_two demo"},
  10. {NULL, NULL, 0, NULL} /* sentinel */
  11. };

Every struction consists of the function name you want to expose to the Python developers, a pointer to the function constructed in the last step, a flag to identify the type of the function and a help string. The last structure must be something like the example above, marking the end of the table.

initialization function

It is a routine to initialize the function at the end of the source code.

  1. PyMODINIT_FUNC
  2. initdiff_calulate(void)
  3. {
  4. /* Create the module and add the functions */
  5. (void)Py_InitModule("diff_calulate", calculate_methods);
  6. }

diff_calculate will then be the package name. The initialization function name is init<PACKAGE_NAME>.

one more thing: header files

Python.h should be included.

If you run gcc example.c -o examle in the command line, you will get a fatal error saying that Python.h is not found. This is because the .h file is not in the default search path of clang. You should look for the right version of Python.h. For me, it is /usr/include/python2.7. But by using the setup.py method, we do not have to worry about it.

Glue C Source Code to Python

The Python package distutils provides us with a great way to setup the Python module with nearly no effort.

Continuing the example above, we can create a file setup.py in the same directory:

  1. from distutils.core import setup, Extension
  2. module = Extension('diff_calculate',sources=['example.c'])
  3. setup(
  4. ext_modules = [module]
  5. )

Note that the first arguments of Extension class must be the same as the package name. You can also add name=...,version=... arguments to setup(...) as you like. Then in the command line, run python setup.py build. You will obtain a build directory, in which you can find diff_calculate.so-- this is the package you can directly put into use.

Use the package

To use the package, import will do:

  1. >>> import diff_calculate
  2. >>> diff_calculate.value_at_one(100)
  3. 2.7319990264290435
  4. >>> diff_calculate.value_at_two(100)
  5. 7.540366073866224

Summary

Writing C extension for Python is easy. In practice, to rewrite the pure C code to a Python extension, we can just modify an example code because most of the steps are very similar. A great tutorial on Python C extension is hosted on tutorialspoint.

Enjoy Python and use Python to do scientific computing!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注