Python与C语言扩展相结合

目录

 

1. Python/C API

1.1 引入 Python.h

1.2 包装 Function

1.3 声明 Module Methods 列表

1.4 定义 Module 的结构

1.5 定义Module Initialization Method

1.6 建立 Extension Module

2. ctypes

2.1 编写C语言版程序

2.2 建立 Shared Library

2.3 引入 Library

3. SWIG

3.1 创建C语言程序版本

3.2 建立 Interface File

3.3 产生 Wrapper File

3.4 建立 Shared Library


1. Python/C API

首先介绍最基本的方式,通过Python/C API来实现。

Python extension module 是Python官方提供Python以外的语言建立且能够让Python调用的module,官方文档地址: docs.python.org/3/extending… ,建议大家在写C扩展程序的时候,都拜读一下。

首先给大家展示一下 Python 版的斐波拉契:

def fib_recursive(n):
    if n < 2:
        return n
    return fib_recursive(n - 1) + fib_recursive(n - 2)
    
start_ts = time.time()
print(fib_recursive(35))
print(time.time() - start_ts)

# 运行结果:
# 9227465
# 3.8501219749450684
复制代码

如果将此程序改为 C 语言程序,命名为 speedup_fib.c,如何进行改写,需要如下步骤:

1.1 引入 Python.h

Python/C API 是C语言里建立 Python extension module 的煤介,必须先引入 Python.h 头文件。

// content of speedup_fib.c
#include <Python.h>

long long _fib(long long n){
    if(n < 2)
        return n;
    else
        return _fib(n-1) + _fib(n-2);
};
复制代码

引入 Python.h,写一个 C 语言版本的 斐波拉契。

1.2 包装 Function

在 Python 中一切皆对象,对应到C语言中是 PyObject,所以要将原来的function包装一下,让参数和返回值均为 PyObject

官方提供了三种参数形式:

  • (PyObject *self)
  • (PyObject *self, PyObject *args)
  • (PyObject *self, PyObject *args, PyObject *kwargs)

其中 args 表示 positional arguments, kwargs 是 keyword arguments.

要把 Python 中的这些 arguments 转为 自己定义的 C 语言参数的话,还需要调用几个API,具体可以参考官方文档 docs.python.org/3/c-api/arg… 截图如下。

 

常用的就是2个。

  • int PyArg_ParseTuple(PyObject *args, const char *format, ...)
  • int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)

第一个处理常规带 positional arguments ,第二个处理带 positional arguments 和 keyword arguments.

其中 format 是格式化参数类型,比如长整型或者字符串等,具体参考官方文档: docs.python.org/2.0/ext/par…

因为 斐波拉契 程序没有键值对参数,所以采用第一个函数,且参数需要格式化为长整型,查询文档长整型是 l 表示。

根据上述解释后,最终包装后的程序为:

// content of speedup_fib.c
static PyObject *fib(PyObject *self, PyObject *args) {

   long long n; // 定义 参数

   long long res; // 定义返回值
   
   // 将参数进行包装,并格式化为长整型l,如果包装失败,则返回NULL.
   if (!PyArg_ParseTuple(args, "l", &n))
       return NULL;
   // 调用C语言版本的斐波拉契,同时传入包装好的参数n
   res = _fib(n);
   // 将返回值用  Py_BuildValue 包成 PyObject 传给 Python
   return Py_BuildValue("l", res);
};
复制代码

包装的 斐波拉契 函数定义为fib,最后返回值也需要进行包装,将C语言的返回值打包成 PyObject

1.3 声明 Module Methods 列表

把 module 的函数进行一一包装后,要建立这个module的method列表,目的就是声明每一个包装函数和C语言函数的对应关系,以及函数是以哪种形式进行传入,格式为:

{name, method, flags, doc}
即 {名称,包装函数,哪种argument形式, 描述}
复制代码

flags 的标识可以参考官方文档: docs.python.org/3/c-api/str…

比较多的就是 METH_VARARGS 和 METH_KEYWORDS,分别对应 args 和 keywords。

所以根据上述描述,定义的Module Methods 为:

// content of speedup_fib.c
static PyMethodDef SpeedupFibMethods[] = {
    {"speedup_fib", (PyCFunction) fib, METH_VARARGS, "fast fib"},
    {NULL, NULL, 0, NULL} // 以 NULL 作结
};
复制代码

1.4 定义 Module 的结构

建立了 Module Methods 之后,还需要定义Module的结构,需要这些信息去创建一个 module object。格式为:

{base, name, doc, size, module methods 表}
即 {PyModuleDef_HEAD_INIT, 名字, 描述, 分配内存大小, module 方法列表}
复制代码

相关定义可以参考官方文档: docs.python.org/3/c-api/mod…

根据上面描述,定义 Module 结构函数为:

// content of speedup_fib.c
static struct PyModuleDef speedup_fib_module = {
    PyModuleDef_HEAD_INIT,
    "speedup_fib",
    "A module containing methods with faster fib.",
    -1, // global state
    SpeedupFibMethods
};
复制代码

1.5 定义Module Initialization Method

接下来定义 Module Initialization Method,目的是根据module结构信息去创建一个module object,而这个module object就可以供Python调用。

需要注意Module Initialization Method必须以PyInit_开头。

// content of speedup_fib.c
PyMODINIT_FUNC PyInit_speedup_fib() {
  return PyModule_Create(&speedup_fib_module);
}
复制代码

1.6 建立 Extension Module

上面5步已经把 speedup_fib.c 中的代码部分写完,接下来需要创建一个 setup.py,通过 Distutils 模块将C语言模块创建出来。

# content of setup.py
from distutils.core import setup, Extension
speedup_fib_module = Extension('speedup_fib', sources=['speedup_fib.c'])

setup(
    name='SpeedupFib',
    description='A package containing modules for speeding up fib.',
    ext_modules=[speedup_fib_module],
)
复制代码

首先指定哪个 C 程序中的 哪个方法,然后调用 setup 建立扩展文件。

通过以下命令:

python3 setup.py build_ext --inplace
复制代码

会在当前文件夹建立一个 speedup_fib.cpython-37m-darwin.so 文件,接下来就可以直接在Python中进行调用。

测试一下:

from speedup_fib import speedup_fib
start_ts = time.time()
print(speedup_fib(35))
print(time.time() - start_ts)

# 运行结果:
# 9227465
# 0.054654836654663086
复制代码

对比之前的Python程序,速度快了80倍,如果n更大,速度差距会更大。

2. ctypes

如果觉得第一种方式过于繁琐,接下来介绍 ctypes,ctypes是Python提供的一个libray,可以让Python进入外部的dynamic-link library (DLL) 或 shared library 来调用其中的函数。

这样不再需要关注Python与C相关的API,专注写C函数即可。

2.1 编写C语言版程序

这一步在任何方式中都不能省,首先还是编写C语言版本的程序。

// content of speedup_fib.c
long long fib(long long n){
    if(n < 2)
        return n;
    else
        return fib(n-1) + fib(n-2);
};
复制代码

是不是很简单,只需要专注写函数,连头文件都不需要。

2.2 建立 Shared Library

这一步需要用到 gcc 工具,如果没有的,需要先安装。

gcc -shared -fPIC speedup_fib.c -o speedup_fib.so
复制代码

通过上述命令将 speedup_fib.c 产生一个 speedup_fib.so 文件。

2.3 引入 Library

接下来就简单了,只需要 ctypes 提高的方法,引入 speedup_fib.so 文件,然后就可以进行 Python 运行了。

# content of fib.py

from ctypes import *
func = cdll.LoadLibrary('./speedup_fib.so')

start_ts = time.time()
print(func.fib(35))
print(time.time() - start_ts)
复制代码

运行上述 fib.py 文件 得到结果:

9227465
0.06056809425354004
复制代码

3. SWIG

SWIG (Simplified Wrapper and Interface Generator) 是更加通用和全面的工具,支持 Python、Perl、Ruby等多种语言.

首先需要先安装 SWIG,如果是 mac 环境 直接 brew install swig即可,window环境参考官网: www.swig.org/Doc3.0/Pref…

3.1 创建C语言程序版本

这一步不可少,但是在swig里需要命令为头文件.h。

// content of speedup_fib.h
long long fib(long long n){
    if(n < 2)
        return n;
    else
        return fib(n-1) + fib(n-2);
};
复制代码

3.2 建立 Interface File

接下来建立接口文件,也可以说是描述接口的档案,习惯命名为 *.i or *.swg。

接下来定义一个 speedup_fib.i

// content of speedup_fib.i

/* 定义 module名称 */
%module speedup_fib

/*导入定义的 speedup_fib.h*/
%{
#include "speedup_fib.h"
%}
/* 告诉 SWIG 定义的 function 或 variable */
long long fib(long long n);
复制代码

上述定义的 speedup_fib.i中,第一步定义Module名称,第二步引入定义的 speedup_fib.h,里面swig会调用里面的函数,第三步是声明函数。

3.3 产生 Wrapper File

通过 SWIG 将 Interface File 生成 extension module 的 speedup_fib.py 和 wrapper file 的 speedup_fib_wrap.c。

命令如下:

swig -python speedup_fib.i
复制代码

当前文件夹会多2个文件: speedup_fib.py 和 speedup_fib_wrap.c。

3.4 建立 Shared Library

这一步跟 Python/C API 创建扩展模块,用用 setup.py 和 Distutils 建立 shared library:

# content of setup.py

from distutils.core import setup, Extension

# Extension module name 要有底线前缀
speedup_fib_module = Extension('_speedup_fib', sources=['speedup_fib_wrap.c'])
setup(
    name='SpeedupFib',
    description='A package containing modules for speeding up performance.',
    ext_modules=[speedup_fib_module],
)
复制代码

注意 Extension module name 要有底线前缀。

接下来下面命令:

python3 setup.py build_ext --inplace
复制代码

会在当前文件夹建立一个 _speedup_fib.cpython-37m-darwin.so 文件,接下来就可以直接在Python中进行调用。

# content of fib.py

from speedup_fib import fib

start_ts = time.time()
print(fib(35))
print(time.time() - start_ts)
复制代码

运行 fib.py, 运行结果为:

9227465
0.05449485778808594

猜你喜欢

转载自blog.csdn.net/ytp552200ytp/article/details/93467230
今日推荐