Bob's Blog

Web开发、测试框架、自动化平台、APP开发、机器学习等

返回上页首页

Python与C扩展的几种方式



python的应用面很广,但是python有个被人诟病的问题就是性能不够理想。不过我们却能看到python在类似科学计算相关的领域仍能发挥作用,这就显得比较矛盾。这是因为python发挥了它的胶水一般的特性,python对于将业务实现和将想法实现是比较快捷方便的,在需要效率和性能的地方则是可以用C或C++来实现。最常用的python(即cpython)与c是比较亲和的,像一些常用的库比如numpy是用c写的。python能方便地与c代码结合在一起。

这里分别介绍ctypes,cython,swig,cffi, python/c api。

ctypes

python的一个模块ctypes可以方便地调用c代码。ctypes提供了一些与c兼容的数据类型和方法,可以调用windows下的.dll文件或linux下的.so文件。

假设我们先写一段简单的c代码:

int add_int(int, int);
float add_float(float, float);

int add_int(int num1, int num2){
    return num1 + num2;
}

float add_float(float num1, float num2){
    return num1 + num2;
}

因为自己目前用mac,就编译为.so文件:

gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c

此时可以在python中用ctypes来调用这个so文件:

import ctypes

adder = ctypes.CDLL('./adder.so')

result_int = adder.add_int(4, 5)
print(result_int)

a = ctypes.c_float(5.25)
b = ctypes.c_float(2.81)
add_float = adder.add_float
add_float.restype = ctypes.c_float
result_float = add_float(a, b)
print(result_float)

 

cython

cython是一种编程语言,以类似python的语法(不相同)来编写c扩展并可被python调用。cython可以将python代码或者python与c或者的pyx代码转换为c,以此提升运行效率;也可辅助python调用c代码中的函数。算是一种常用的提升python性能的方式。

先安装cython:

pip install cython

接下来准备3个文件,功能是一样的:

# pure_python.py 不转换
def test(x):
    y = 1
    for i in range(1, x+1):
        y *= i
    return y
# cy_python.py  转换
def test(x):
    y = 1
    for i in range(1, x+1):
        y *= i
    return y
# pyx_python.pyx 转换
cpdef int test(int x):
    cdef int y = 1
    cdef int i
    for i in range(1, x+1):
        y *= i
    return y

创建setup.py,并分别指明cy_python.py和pyx_python.pyx:

from distutils.core import setup
from Cython.Build import cythonize
setup(
    ext_modules = cythonize("pyx_python.pyx")  # 执行一次
    # ext_modules = cythonize("cy_python.py")  # 执行一次
)

#additional
#如果有import第三方包,需要类似如下的:
from distutils.core import setup
from Cython.Build import cythonize
import numpy
setup(
    ext_modules=cythonize("try_numpy.pyx"),
    include_dirs=[numpy.get_include()]
)

执行命令分别生成so文件:

python setup.py build_ext --inplace

再新增一个测试文件看看所需时间的对比,可以看出pyx的执行时间非常短:

import cy_python
import pure_python
import pyx_python
import time

n = 100000

start = time.time()
pure_python.test(n)
end = time.time()
print(end - start)
time.sleep(1)

start = time.time()
cy_python.test(n)
end = time.time()
print(end - start)
time.sleep(1)

start = time.time()
pyx_python.test(n)
end = time.time()
print(end - start)
time.sleep(1)

 

swig

swig可以将c代码转换成多种语言的扩展,虽然过程显得麻烦点。

先在mac上安装swig:

brew install swig

按照官方样例,先增加一个example.c文件:

#include <time.h>
double My_variable = 3.0;

int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

int my_mod(int x, int y) {
    return (x%y);
}

char *get_time()
{
    time_t ltime;
    time(&ltime);
    return ctime(&ltime);
}

再添加一个interface文件 example.i:

/* example.i */
 %module example
 %{
 /* Put header files here or function declarations like below */
 extern double My_variable;
 extern int fact(int n);
 extern int my_mod(int x, int y);
 extern char *get_time();
 %}

 extern double My_variable;
 extern int fact(int n);
 extern int my_mod(int x, int y);
 extern char *get_time();

接着运行一些命令:

swig -python example.i
gcc -c example.c example_wrap.c -I/Users/test/.pyenv/versions/3.6.8/include/python3.6m
ld -shared example.o example_wrap.o -o _example.so

如果在运行这几个命令的时候遇到了提示找不到Python.h的错误,往往是-I后面给的路径不对,比如用了pyenv或者virtualenv,此时查找下Python.h的路径(find ./ -name '*Python.h*'),将对应路径加上即可。

如果遇到了提示“ld: unknown option: -shared”,那么可以换做用下面的方式。

先新增一个setup.py:

from distutils.core import setup, Extension

example_module = Extension('_example', sources=['example_wrap.c', 'example.c'])
setup(name='example', ext_modules=[example_module], py_modules=["example"])

再重新运行命令:

swig -python example.i
python setup.py build_ext --inplace

此时便可调用原c代码中的方法了:

import example
example.fact(5)
example.my_mod(7,3)
example.get_time()

 

cffi

cffi是python的一个库,可以在python文件中调用c代码,和ctypes类似,但不同的是不需要先编译为so文件,允许直接在python代码中加入c代码的语句。

比如这段代码:

from cffi import FFI

ffi = FFI()
ffi.cdef("""
    int add(int a, int b);
    int sub(int a, int b);
    """)

lib = ffi.verify("""
    int add(int a,int b){
        return a+b;
    }
     int sub(int a,int b){
        return a-b;
    }
    """)
print(lib.add(10,2))
print(lib.sub(10,2))

cffi还可以生成扩展模块给其他python脚本用,也可用于调用外部c的函数。

 

python c api

原生的python c api的方式据说是最被广泛使用的。在c代码中操作python的对象。但需要特定的方式来声明python对象,往往能看到PyObject的字样。比如PyObject为PyListType时,可以用PyListSize()来获取获取个数,类似python中的len(list)。

需要先引入Python.h这个头文件,里面定义了所有需要的类型(Python对象类型的表示)和函数定义(对Python对象的操作)。

现在用c来做一个最简单的输出helloworld,先添加一个c_py_hello.c文件:

#include <Python.h>

// Function 1: A simple 'hello world' function
static PyObject* helloworld(PyObject* self, PyObject* args)
{
    printf("Hello World\n");
    return Py_None;
}

// Our Module's Function Definition struct
// We require this `NULL` to signal the end of our method
// definition
static PyMethodDef myMethods[] = {
    { "helloworld", helloworld, METH_NOARGS, "Prints Hello World" },
    { NULL, NULL, 0, NULL }
};

// Our Module Definition struct
static struct PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "c_py",
    "Test Module",
    -1,
    myMethods
};

// Initializes our module using our above struct
PyMODINIT_FUNC PyInit_c_py(void)
{
    return PyModule_Create(&myModule);
}

新建一个setup.py:

from distutils.core import setup, Extension

setup(name = 'c_py', version = '1.0',  \
   ext_modules = [Extension('c_py', ['c_py_hello.c'])])

运行:

python setup.py install

此时该模块就在site-packages中了,可以直接调用:

>>> import c_py
>>> c_py.helloworld()
Hello World

顺便说一句,网上关于addList的对list内数字做总计的样例在目前python3里是用不了的,需要把PyInt_AsLong改成PyLong_AsLong,并把Py_InitModule3换成其他的。如下所示:

//Python.h has all the required function definitions to manipulate the Python objects
#include <Python.h>

 //This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args){

  PyObject * listObj;

  //The input arguments come as a tuple, we parse the args to get the various variables
  //In this case it's only one list variable, which will now be referenced by listObj
  if (! PyArg_ParseTuple( args, "O", &listObj))
    return NULL;

  //length of the list
  long length = PyList_Size(listObj);

  //iterate over all the elements
  long i, sum =0;
  for(i = 0; i < length; i++){
    //get an element out of the list - the element is also a python objects
    PyObject* temp = PyList_GetItem(listObj, i);
    //we know that object represents an integer - so convert it into C long
    long elem = PyLong_AsLong(temp);
    sum += elem;
  }

  //value returned back to python code - another python object
  //build value here converts the C long to a python integer
  return Py_BuildValue("i", sum);
}

//This is the docstring that corresponds to our 'add' function.
static char addList_docs[] =
    "add( ): add all elements of the list\n";

/* This table contains the relavent info mapping -
  <function-name in python module>, <actual-function>,
  <type-of-args the function expects>, <docstring associated with the function>
*/
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
    {NULL, NULL, 0, NULL}
};


// Our Module Definition struct
static struct PyModuleDef myModule = {
    PyModuleDef_HEAD_INIT,
    "addList",
    "Add all items in list",
    -1,
    addList_funcs
};

// Initializes our module using our above struct
PyMODINIT_FUNC PyInit_addList(void)
{
    return PyModule_Create(&myModule);
}

一样的添加setup.py:

from distutils.core import setup, Extension

setup(name='addList', version='1.0',  \
      ext_modules=[Extension('addList', ['c_py_list.c'])])

运行python setup.py install后便可调用了:

>>> import addList
>>> l = [3, 77, 1, 22]
>>> addList.add(l)
103

 

参考链接:

https://docs.python.org/zh-cn/3/extending/extending.html

https://book.pythontips.com/en/latest/python_c_extension.html

https://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p15_c_extensions.html

https://medium.com/mathematicallygifted/cython-use-it-to-speed-up-python-code-with-examples-cd5a90b32fc7

下一篇:  Django添加自定义的404等错误页面
上一篇:  Python加Selenium自动化测试知乎网站(七)设置检查点

共有0条评论

添加评论

暂无评论