【前言】:最近在做的工程因为函数是用c写的,但是需要对外提供pyhton接口,研究了一下方法,发现几种方法调用,本文主要是介绍python/c api方法的实现

代码参照:https://github.com/chanzhennan/Extend_Python

【python / c】

python/c调用有三种方法,网上有大量的资料

【python/c api extension】:最多人用的一种方法,python端基本不动,改变c端代码,需要再写接驳函数

【ctypes / cython】:最方便的调用,c端代码不动,python端需要改变大量代码,比如类型,c_int c_float 直接将python改成c风格

【swig】:没用过,第三方库,可以自动大量生成。方便,但是内存容易出错。

我的老大说之前的项目用过swig,但是内存管理就是个噩梦,用起来各种内存报错但是不知道在哪里发生。

【实现】

【c语言函数】

那么我们先有一段简单的c函数,

#sample.h

/* sample.h */#include <math.h>extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);typedef struct Point {double x,y;
} Point;extern double distance(Point *p1, Point *p2);

#sample.c

/* sample.c */
#include "sample.h"/* Compute the greatest common divisor */
int gcd(int x, int y) {int g = y;while (x > 0) {g = x;x = y % x;y = g;}return g;
}/* Test if (x0,y0) is in the Mandelbrot set or not */
int in_mandel(double x0, double y0, int n) {double x=0,y=0,xtemp;while (n > 0) {xtemp = x*x - y*y + x0;y = 2*x*y + y0;x = xtemp;n -= 1;if (x*x + y*y > 4) return 0;}return 1;
}/* Divide two numbers */
int divide(int a, int b, int *remainder) {int quot = a / b;*remainder = a % b;return quot;
}/* Average values in an array */
double avg(double *a, int n) {int i;double total = 0.0;for (i = 0; i < n; i++) {total += a[i];}return total / n;
}/* Function involving a C data structure */
double distance(Point *p1, Point *p2) {return hypot(p1->x - p2->x, p1->y - p2->y);
}/* n:longth of array */
void clip(double *a, int n, double min, double max, double *out) {double x;for (; n >= 0; n--, a++, out++) {x = *a;*out = x > max ? max : (x < min ? min : x);}
}

我们需要对这几个函数扩展成python api,我们需要新建两个文件pysample.h pysamply.c 来解析这几个函数,我删减了大部分函数,留下了具有代表性的,要跑代码的同学可以直接去github上面下载,然后跑最后一步 python3 setup.py install 就可以

#include "Python.h"
#define PYSAMPLE_MODULE  //<---pysample.h:16
#include "pysample.h"/* pyObject 与 Point 的互相转换*/
static PyObject *PyPoint_FromPoint(Point *p, int must_free) {return PyCapsule_New(p, "Point", must_free ? del_Point : NULL);
}/* Utility functions */
static Point *PyPoint_AsPoint(PyObject *obj) {return (Point *) PyCapsule_GetPointer(obj, "Point");
}static _PointAPIMethods _point_api = {PyPoint_AsPoint,PyPoint_FromPoint
};/* Create a new Point object */
static PyObject *py_Point(PyObject *self, PyObject *args) {Point *p;double x,y;if (!PyArg_ParseTuple(args,"dd",&x,&y)) {return NULL;}p = (Point *) malloc(sizeof(Point));p->x = x;p->y = y;return PyPoint_FromPoint(p, 1);
}static PyObject *py_distance(PyObject *self, PyObject *args) {Point *p1, *p2;PyObject *py_p1, *py_p2;double result;if (!PyArg_ParseTuple(args,"OO",&py_p1, &py_p2)) {return NULL;}if (!(p1 = PyPoint_AsPoint(py_p1))) {return NULL;}if (!(p2 = PyPoint_AsPoint(py_p2))) {return NULL;}result = distance(p1,p2);return Py_BuildValue("d", result);
}#include "Python.h"/* Module method table */
static PyMethodDef SampleMethods[] = {{"Point", py_Point, METH_VARARGS},{"distance", py_distance, METH_VARARGS, "Function involving a C data structure"},{ NULL, NULL, 0, NULL}
};/* Module structure */
static struct PyModuleDef samplemodule = {PyModuleDef_HEAD_INIT,"sample",           /* name of module */"A sample module",  /* Doc string (may be NULL) */-1,                 /* Size of per-interpreter state or -1 */SampleMethods       /* Method table */
};/* Module initialization function */
PyMODINIT_FUNC
PyInit_sample(void) {PyObject *m;PyObject *py_point_api;m = PyModule_Create(&samplemodule);if (m == NULL)return NULL;/* Add the Point C API functions */py_point_api = PyCapsule_New((void *) &_point_api, "sample._point_api", NULL);  //<---pysample.h:23if (py_point_api) {PyModule_AddObject(m, "_point_api", py_point_api);}return m;
}

我们会发现,整个代码充斥大量的PyObject,其实这个类型就是python的核心,在python的世界里面,每个函数就像一个房间,每个房间的里面的实现没有任何限制,但是这个房间的出口和入口只能是基于PyObject这个基类的继承或者它本身。这就有点像乐高或者是拼图,每个拼图的颜色都不一样,但是拼图间接驳部分是有严格的标准。所以我们只需要把我们的输入和输出全部转化为PyObject就好了。那么c语言的特性,输入无外乎传统的值 【int double float】或者是指针【pointer】,又或者指针和变量的集合【struct】。

那么事情就变得简单多了,我们发现有两个关键函数,这两个函数就是在接驳函数的输入输出做point 和 pyObject的转换

/* point -> pyObject */
static PyObject *PyPoint_FromPoint(Point *p, int must_free)
{return PyCapsule_New(p, "Point", must_free ? del_Point : NULL);
}/* pyObject -> Point */
static Point *PyPoint_AsPoint(PyObject *obj)
{return (Point *) PyCapsule_GetPointer(obj, "Point");
}
#使用
pyObject* 接驳函数(pyObject* _py1){1, _py1 转换为 _struct #(PyPoint_AsPoint)2,C语言函数3, _struct 转换为 _py2() #(PyPoint_FromPoint)4 return _py2}

这两个函数,是对Point结构体与PyObject的互相转换,他们在我们写的pysample接口函数中出现,主要就是把接受的pyobject转换为c 结构体,经过计算完之后再打包成PyObject返回。e.g.

static PyObject *py_distance(PyObject *self, PyObject *args) {Point *p1, *p2;PyObject *py_p1, *py_p2;double result;if (!PyArg_ParseTuple(args,"OO",&py_p1, &py_p2)) {return NULL;}//接收if (!(p1 = PyPoint_AsPoint(py_p1))) {return NULL;}if (!(p2 = PyPoint_AsPoint(py_p2))) {return NULL;}result = distance(p1,p2);//返回return Py_BuildValue("d", result);
}

因为args是可变参数列表,所以我们写了两个Point* 两个PyObject*来接收,

PyArg_ParseTuple(args,"OO",&py_p1, &py_p2)

会把参数按照‘O’ Object的方式解释,放到py_p1和py_p2之中,然后再经过PyPoint_AsPoint转换放到Point指针进行计算。

最后的结构用Py_BuildValue把int封装成PyObject类型返回。这样就算完成了接驳函数,下面是要对接驳函数进行python的注册

【注册表】

写完python的接驳函数,我们需要对接驳函数在python中 注册,就是在python中以什么名字,函数是什么属性进行指定

#include "Python.h"/* Module method table */
static PyMethodDef SampleMethods[] = {{"Point", py_Point, METH_VARARGS},{"distance", py_distance, METH_VARARGS, "Function involving a C data structure"},{ NULL, NULL, 0, NULL}
};/* Module structure */
static struct PyModuleDef samplemodule = {PyModuleDef_HEAD_INIT,"sample",           /* name of module */"A sample module",  /* Doc string (may be NULL) */-1,                 /* Size of per-interpreter state or -1 */SampleMethods       /* Method table */
};/* Module initialization function */
PyMODINIT_FUNC
PyInit_sample(void) {PyObject *m;PyObject *py_point_api;m = PyModule_Create(&samplemodule);if (m == NULL)return NULL;/* Add the Point C API functions */py_point_api = PyCapsule_New((void *) &_point_api, "sample._point_api", NULL);  //<---pysample.h:23if (py_point_api) {PyModule_AddObject(m, "_point_api", py_point_api);}return m;
}

【SampleMethods】是注册表类型,最后一项一定是 { NULL, NULL, 0, NULL}表示结束,子集里面第一项是在python中调用的函数名称,第二项是接驳函数,第三项是【属性】,第四项是doc 文本解释

【属性】

METH_VARARGS :函数为长短变量,通常采用这个属性,数量在于PyArg_ParseTuple解析的数量

METH_KEYWORDS :引用key属性

METH_NOARGS:没有参数

/* Flag passed to newmethodobject */
/* #define METH_OLDARGS  0x0000   -- unsupported now */
#define METH_VARARGS  0x0001
#define METH_KEYWORDS 0x0002
/* METH_NOARGS and METH_O must not be combined with the flags above. */
#define METH_NOARGS   0x0004
#define METH_O        0x0008

【PyInit_sample】写安装函数,让python编译安装

我们对模块的名字,类型,接驳函数名字,类型都指定了,注册函数也写完了,下面需要走python脚本进行编译安装生成.so 以便我们写python的时候import

【setup.py】

模块的安装脚本十分简单,安装脚本只要指定要编译的文件,和模块名称即可。

# setup.py
from distutils.core import setup, Extensionsetup(name='sample',ext_modules=[Extension('sample',['pysample.c', 'sample.c'])]
)

如果需要依赖第三方库可以以下脚本参考

#pragma once
from distutils.core import setup, Extensionsetup(name='paillier',ext_modules=[Extension('paillier',include_dirs = ['~/czn/code/Python_C_Paillier'],libraries = ['gmp'],library_dirs = ['~/czn/code/Python_C_Paillier'],sources=['paillier.c','pypaillier_module.c', 'tools.c', 'paillier_manage_keys.c'])]
)

这个脚本依赖了gmp.so,只需要把头和体的路径指定就可以引入

运行

python3 setup.py install

会在当前和python的【python3.7/site-packages/】路径下生成.so

【常见问题】

函数报错:TypeError: function takes exactly 3 arguments (2 given)

fuc = paillier.encrypt(mng, csv)

接驳函数会根据PyArg_ParseTuple来判定输入参数个数,输入参数的时候要对应上

【贤者之路】Python/c Api 扩展解析相关推荐

  1. python ctypes实现api测试_Python与C之间的相互调用(Python C API及Python ctypes库)

    2010-01-24 17:58 14237人阅读 评论(11) 我实现 Python C API 此部分可以参考我原来的文章< 准备工作: 闲话少说,看看Python C API.事实上,Py ...

  2. Python与C之间的相互调用(Python C API及Python ctypes库)

    2010-01-24 17:58 14237人阅读 评论(11) 收藏 举报 目录(?)[-] Python C API 准备工作: C中内嵌Python 获取返回值 利用C扩展Python Pyth ...

  3. 编写python扩展模块_《深度剖析CPython解释器》27. 使用Python/C API编写扩展模块:编写扩展模块的整体流程...

    楔子 到目前为止,我们已经介绍了很多关于解释器方面的内容,本来接下来应该要说内存管理的,但是个人觉得应该对前面的系列做一个总结.而最好的方式,就是使用Python/C API编写扩展模块,个人是这么认 ...

  4. Python调用C函数的方法以及如何编写Python的C扩展

    正文共1535个字,2张图,预计阅读时间4分钟. 01 前言 前言属闲聊,正文请转后. 标题比较长,其实"如何用Python调用C的函数"以及"如何编写Python的C扩 ...

  5. python自带网页解析器_Python爬虫Chrome网页解析工具-XPath Helper

    之前就说过Python爬虫中Xpath的用法,相信每一个写爬虫.或者是做网页分析的人,都会因为在定位.获取XPath路径上花费大量的时间,在没有这些辅助工具的日子里,我们只能通过搜索HTML源代码,定 ...

  6. Kotlin系列之集合和函数式API完全解析-上篇

    简述: 今天带来的是Kotlin浅谈系列的第八讲,这讲我们一起来聊聊Kotlin这门语言对函数式编程的支持.我们都知道在kotlin这门语言中函数荣升成为了一等公民,所以在支持函数式编程的方面,Kot ...

  7. 如何用 Python 和 API 收集与分析网络数据?

    摘自 https://www.jianshu.com/p/d52020f0c247 本文以一款阿里云市场历史天气查询产品为例,为你逐步介绍如何用 Python 调用 API 收集.分析与可视化数据.希 ...

  8. 使用Flask和Connexion构建和记录Python REST API

    If you're writing a web application, then you're probably thinking about making HTTP calls to your s ...

  9. Python与C之间的相互调用(Python C API及Python ctypes库)【转】

    准备工作: 闲话少说,看看Python C API.事实上,Python C API比起Lua的API了来说,清晰了很多,这也符合Pythonic的风格,就算这时Python C API是设计给C语言 ...

  10. python之XML文件解析

    python对XML的解析 常见的XML编程接口有DOM和SAX,这两种接口处理XML文件的方式不同,当然使用场合也不同. python有三种方法解析XML,分别是SAX,DOM,以及ElementT ...

最新文章

  1. 唐山一个葬礼上的豪华车队
  2. UA SIE545 优化理论基础1 例题2 Farkas定理与相关结论
  3. hybris backoffice和产品主数据相关的一些sample data
  4. python安装caffe_Linux下caffe的安装
  5. 我应该做哪个行业的销售
  6. java 最佳主键_最佳Java 8书籍
  7. (QT学习笔记):QListWidget、QTreeWidget、 QTableWidget的基本使用
  8. 使用 Iperf 测试软路由性能
  9. 百度编辑器Ueditor多文本域实现
  10. 【整理】1、C网和G网双模手机是否可同时支持C网和G网上网?2、上网的同时是否可以打电话?3、呼叫等待和呼叫保持的概念与“双通”手机
  11. android 百度浏览器内核,百度推手机浏览器Android版 移植webkit内核
  12. UE4 C++与UMG
  13. word只读模式怎么改成编辑模式
  14. 关于粽子的生产产线提速
  15. HCIP-DATACOM H12-831(1-20)
  16. windows系统运维基础
  17. 篱笆家装宝典之四——瓷砖
  18. 运维部门工作总结_运维部技术工作总结
  19. link.sct解析
  20. 如何退出Vi或Vim编辑器

热门文章

  1. Windows安装TortoiseSVN
  2. WordPress Feed跳转
  3. 音乐鉴赏 周海宏 网络课程 题库(Ctrl+f查找)点赞哦
  4. 好用文件整理工具,需要速来
  5. Windows系统目录及常用快捷键
  6. php 字符串转日期格式
  7. 传统图像特征提取方法列表
  8. 创建jsp文件时报8080端口被占用,解决办法
  9. 台式计算机读不到u盘怎么回事,电脑读不出u盘怎么办
  10. 龙芯计算机价格,龙芯电脑目标售价有望逼近1000元