C & Python

Python调用C动态链接库、使用C编写Python扩展

By Damnever on July 16, 2015

CONTENT:


调用动态链接库里的函数

ctypes提供了一些方法使Python与C的数据类型相兼容,并且允许Python调用动态链接库或者共享库中的函数。

下面是一段计数排序的C语言代码(counting_sort.c):

#include <stdio.h>
#include <stdlib.h>

void counting_sort(int *nums, int len)
{
    int i, tmp, max = max_num(nums, len);
    int *cnums = (int *)malloc(max * sizeof(int));
    int *bp = nums;

    for(i=0; i < max; i++){
        cnums[i] = 0;
    }

    while((len--) > 0){
        cnums[*bp - 1] += 1;
        bp++;
    }

    bp = nums;
    for(i=0; i < max; i++){
        tmp = cnums[i];
        while((tmp--) > 0){
            *bp++ = i + 1;
        }
    }

    free(cnums);
    cnums = NULL;
}

int max_num(int *nums, int len)
{
    int max = 0;
    while((len--) > 0){
        if(*nums > max){
            max = *nums;
        }
        nums++;
    }
    return max;
}

void print_arr(int *nums, int len)
{
    while((len--) > 0){
        printf("%d ", *nums++);
    }
    printf("\n");
}

将它编译成动态链接库:

gcc counting_sort.c -fPIC -shared -o counting_sort.so

ctypes里面详细的说明了如何创建需要的C数据类型,在调用函数并传参的时候需要将Python数据类型转换成相应的C数据类型。

下面是调用C函数的Python代码:

from __future__ import print_function

from ctypes import *


lib_path = './counting_sort.so'

counting_sort = cdll.LoadLibrary(lib_path)

Integers = c_int * 10
int_arr = Integers(2, 3, 5, 1, 4, 9, 8, 7, 6, 5)

length = c_int(len(int_arr))
counting_sort.print_arr(int_arr, length)
counting_sort.counting_sort(int_arr, length)
counting_sort.print_arr(int_arr, length)

for i in int_arr:
    print(i, end=' ')


##=>

# 2 3 5 1 4 9 8 7 6 5

# 1 2 3 4 5 5 6 7 8 9

# 1 2 3 4 5 5 6 7 8 9

这种方式用来封装(适配)已存在的动态链接库,并给Python调用还是非常好的,一般不会有人通过这个自己编写C来扩展Python,如果你非要这样当我没说……


编写Python的C扩展

画虎,下面是一段计算Python列表里面所有数字和的C代码:

#include <python2.7/Python.h>

static PyObject *sum_num_list(PyObject *self, PyObject *arg)
{
    int idx, size;
    PyObject *list = NULL, *item = NULL;
    PyObject *sum = PyLong_FromLong(0);

    if(!PyArg_ParseTuple(arg, "O", &list)){
        return NULL;
    }
    if(!PyList_Check(list)){
        Py_RETURN_NONE;
    }
    size = PyList_Size(list);

    for(idx = 0; idx < size; idx++) {
        item = PyList_GetItem(list, idx);
        if(!PyNumber_Check(item)){
            continue;
        }
        sum = PyNumber_Add(sum, item);
    }
    return sum;
}


static PyMethodDef sn_method[] = {
    {"sum_num_list", (PyCFunction)sum_num_list, METH_VARARGS, "Sum a list of numbers."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initsumnum()
{
    Py_InitModule3("sumnum", sn_method, "sumnum is C extension module.");
}

可以通过标准库自带的distutilsC扩展编译成动态链接库并打包到相应的dist-package目录下:

from distutils.core import setup, Extension

setup(
    name='sumnum',
    author='Damnever',
    version='0.1',
    ext_modules=[Extension('sumnum', ['sumnum.c'])])

这里直接编译成动态链接库,并在当前目录下启动解释器:

[10:00:00] » gcc sumnum.c -fPIC -shared -o sumnum.so
 /vagrant/Python/Test/C
[10:00:58] » python
Python 2.7.10 (default, Jul 12 2015, 16:54:16)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sumnum
>>> sumnum.sum_num_list([1, 2, 3])
6L
>>> sumnum.sum_num_list([1, 2, 's', 3])
6L
>>> sumnum.sum_num_list([1, 2, 's', 3.2])
6.2
>>> sumnum.__doc__
'sumnum is C extension module.'
>>> sumnum.sum_num_list.__doc__
'Sum a list of numbers.'

编写C扩展,里面有很多东西需要注意。如引用计数就是很重要的,关系到内存使用和垃圾回收,见Objects, Types and Reference Counts。总之,这些东西还有待研究……

具体的接口参考Python/C API Reference Manual