原square.c
程序如下,想把其中的calc_area()
函数封装了给 Python 调用
#include <stdio.h>
#include <stdlib.h>
struct Square {
float length;
float width;
};
typedef struct Square *sq;
float calc_area(sq a) {
float s;
s = a->length * a->width;
return s;
}
int main() {
sq a;
a = malloc(sizeof(struct Square));
a->length = 10.0;
a->width = 3.0;
printf("%f\n", calc_area(a));
}
我写了个头文件square.h
如下,我很少写 C ,所以也不确定写的对不对
#include <stdio.h>
#include <stdlib.h>
struct Square {
float length;
float width;
};
typedef struct Square *sq;
float calc_area(sq a);
然后试着用 Cython ,写了square_wrapper.pyx
如下,主要涉及到指针和自定义结构体,真不知道怎么写了,反正报错
cdef extern from 'square.h':
struct Square:
float length
float width
ctypedef struct Square *sq
float calc_area(sq a)
def c_area(a):
return calc_area(sq a)
请教熟悉 Cython 或者其他如 SWIG 的 V 友,这个简单的 C 程序该如何封装? 已经困扰很久,万分感谢!
1
dant 2017-01-12 14:06:01 +08:00 1
ctypes/cffi
|
2
lyricorpse OP @dant 谢谢,我去学习一下
|
3
enenaaa 2017-01-12 16:24:24 +08:00 1
Square 结构 在 python 里面要用吗。 如果只是引用, 不实际使用里面的字段。 用 PyObject* 相互传递即可。
你也可以直接将指针当作一个长整型数值, 需要的时候再强制转换为指针。 |
4
justou 2017-01-12 16:50:34 +08:00 1
ctypedef struct Square *sq 不符合 cython 语法, 把 struct 去掉
要封装 Square 给 py 的话, 可以在 pyx 中定一个 cdef 的 class, 维护一个指向 struct Square 的指针. 把 cal_area 作为这个 cdef class 的方法 https://gist.github.com/justou/ac94501d664f32872b2ae546099d874c 这是封装 C 库的流程,如果是自定义的扩展模块 ,c/c++ -> cython -> py 这样做其实略繁琐 . 完全可以直接 cython -> py, 简化给 py 写 C 扩展的是 cython 的目的之一. |
5
spice630 2017-01-12 21:31:41 +08:00 1
如果你用 golang ,我可以教你 cgo ,很简单。
/* // go preamble #include "foo.h" */ import "C" func bar(){ C.foo() } |
6
herozem 2017-01-12 23:54:17 +08:00 1
我研究了一下,贴下代码吧。
``` root@arch area: # 首先是 area.h 和 area.c root@arch area: cat area.h #ifndef _AREA_H #define _AREA_H struct Square { float length; float width; }; typedef struct Square *pSquare; float calc_area(pSquare); #endif /* area.h */ root@arch area: cat area.c #include <stdio.h> #include <stdlib.h> #include "area.h" float calc_area(pSquare s) { return s->length * s->width; } int main(void) { pSquare s = (pSquare)malloc(sizeof(struct Square)); if (s == NULL) { printf("memory not enough"); exit(1); } s->length = 10.0; s->width = 3.0; printf("area of the square: %f\n", calc_area(s)); free(s); } root@arch area: # 运行一下 root@arch area: cc area.c && ./a.out && rm a.out area of the square: 30.000000 root@arch area: # 为 area.c 定义包裹的 Cython 头文件 root@arch area: cat carea.pxd cdef extern from "area.h": cdef struct Square: float length float width ctypedef Square *pSquare; cdef float calc_area(pSquare); root@arch area: # 为 python 版本 Square 定义 Cython 头文件 root@arch area: cat py_area.pxd cimport carea cdef class Square: cdef carea.pSquare _square cpdef float calc_area(Square) root@arch area: # 为 python 版本 Square 包裹一下 root@arch area: cat py_area.pyx from cpython.mem cimport PyMem_Malloc, PyMem_Free cimport carea cdef class Square: def __cinit__(self, length, width): self._square = <carea.pSquare>PyMem_Malloc(sizeof(carea.Square)) if not self._square: raise MemoryError("Memory not enough") self._square.length = length self._square.width = width def __dealloc__(self): PyMem_Free(self._square) cpdef float calc_area(self): return carea.calc_area(self._square) root@arch area: # 调用一下 root@arch area: # 哦不,先写好 setup.py ,然后编译 root@arch area: cat setup.py from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize ext_modules = cythonize([ Extension("py_area", ["py_area.pyx", "area.c"]) ]) setup(ext_modules=ext_modules) root@arch area: python3 setup.py build_ext --inplace running build_ext building 'py_area' extension gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -fPIC -I/usr/include/python3.6m -c py_area.c -o build/temp.linux-x86_64-3.6/py_area.o gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong -fPIC -I/usr/include/python3.6m -c area.c -o build/temp.linux-x86_64-3.6/area.o gcc -pthread -shared -Wl,-O1,--sort-common,--as-needed,-z,relro -Wl,-O1,--sort-common,--as-needed,-z,relro build/temp.linux-x86_64-3.6/py_area.o build/temp.linux-x86_64-3.6/area.o -L/usr/lib -lpython3.6m -o /root/tests/area/py_area.cpython-36m-x86_64-linux-gnu.so root@arch area: # 调用 root@arch area: ipython Python 3.6.0 (default, Dec 24 2016, 08:03:08) Type "copyright", "credits" or "license" for more information. IPython 5.1.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: import py_area In [2]: a = py_area.Square(10, 20) In [3]: a.calc_area() Out[3]: 200.0 In [4]: Do you really want to exit ([y]/n)? root@arch area: ``` |
7
herozem 2017-01-12 23:56:06 +08:00
代码缩进全乱了。贴一下 gist: https://gist.github.com/jiajunhuang/25fee5da7e99c1ad01067b440c3e8a50
|
8
lyricorpse OP |
9
lyricorpse OP @justou 另外再请教下,中间这步制成静态库有什么作用?跳过这步似乎不影响给 Python 调用。
!gcc -c square.c -o square.o !ar rcs libsquare.a square.o |
10
herozem 2017-01-13 10:09:41 +08:00 via iPhone 1
py_area.pxd 不是必须的,可以把声明_square 类型那一行移到 py_area.pyx 里。我是看了一遍官方文档,然后看了一下 cython : a guide to python programmers 前几章,又跳回去把官方文档看了一遍。我也是这几天才开始看 cython 的😅如果有 python 和 c 基础的话,仔细读读这两个文档,然后一边看一边自己验证应该不用太久的
|
11
lyricorpse OP @herozem 感谢!我现在发现我真正要 wrap 的复杂 C 程序的函数接口写得很糟糕,在函数中调用了一些全局变量,这些全局变量通过 IO 读取数值,我在 Cython 中该如何处理这种情况?需要把那些全局变量也全部 wrap 成 python 的变量么?
|
12
lyricorpse OP @justou @herozem 我把关于公共变量的问题相关代码放到这个 gist 中去了 https://gist.github.com/lyricorpse/6f9a7123ce64adf8f778d98fb65ec442
square.c 文件中添加了公共变量 glb_f ,请问如何 wrap 能在 Python 中访问修改这个变量? |
13
justou 2017-01-13 14:15:37 +08:00 1
@lyricorpse 我也不是程序员出生, 有做科学计算的需要才开始写程序的
根据个人经验, 要用好 cython 的话需要一定的 c 基础, 要比较熟悉 c 的各种玩法. 我感觉 cython 最大的好处就是把权衡交给程序员, 也可以完全避开 c, 就用 cython 跟 python 的东西, 这几乎可以解决大多数 python 执行效率的问题了, 极少时候, 如果还想继续提升性能, 一般是需要更复杂的并行, 或者无法完全避开 gil 的问题, 就在 cython 中用更多的 c/cpp, 还不行就把这部分完全剥离到 c/cpp 库中完成计算, python 负责预处理后处理, cython 作为桥梁. 反复地看 cython 官方文档, 有空就去逛逛 stackoverflow, 看看相关的问题, 多逛逛相关博客, 不明觉厉的东西要保存下来, 然后逐步的处理, 整理到自己的知识系统, 使用中遇到的问题以及解决方法要详细的记下来, 因为很可能再次遇到, 这时候想起以前遇到过但是忘记解决办法了是很恼火的. 多看看 cython 的相关项目, 因为投身于科学计算, 所以比较关注这方面的库, 比如 cython_gsl, 包装的科学计算库 GNU Scientific Library(GSL); cy_armadillo, 包装 cpp 的 armadillo 库, 都是开源项目, 看这些源码的时候 c/cpp/cython 的熟悉程度是同步提升的, 还有 cython 自带的那些 pxd 是包装 c/cpp 标准库最好的例子. 其实学任何技术都是这个过程吧. cython 的书籍的话我也只有你上面提到的那本.如果你从事科学计算方面, 推荐下张老师 @ruoyu0088, 我是跟着他一路小跑过来的 233, 可以去搜下他的博客, 论坛, 书籍 ------------------------------------------------------------------------------------------------- 如果不是已经无法修改的 C 库的话, 建议还是直接放到 cython 中写, 不必写个.c 来包装. 包装那个全局变量的话: 1.c 的头文件声明 float glb_f; 2.pxd 里面也做相应声明 cdef extern from "square.h": float glb_f 3.pyx 里面的修改 cimport square ... def __init__(self, width, length, global_f=1.0): square.glb_f = global_f # 从这儿设置这个全局变量 self.s_ptr.width = width self.s_ptr.length = length |
14
herozem 2017-01-13 14:22:37 +08:00
@lyricorpse 试了一下直接声明全局变量没成功,编译不过。但是我想可以用曲折的办法就是给全局变量写上 getter/setter 然后把这几个函数包装一下,在 python 层面再做成 property 。
|
15
lyricorpse OP |
16
justou 2017-01-13 14:44:01 +08:00
@lyricorpse 那差不多, 我也是做些计算, 数据分析之类的, 物理生→_→
|
17
ruoyu0088 2017-01-13 19:28:15 +08:00 1
下面是用 cffi 包装的例子:
https://gist.github.com/ruoyu0088/47135d7726abfdc03a61f63d4696cb74 调用 build.py 编译扩展模块,然后运行 test.py 测试。 在 square.h 中声明 extern 的全局变量 glb_f 。在 Python 中可以直接通过 lib.glb_f 设置该全局变量。使用 ffi.new("sq", ...)创建结构体,可以使用字典初始化结构体,也可以通过属性访问结构体的字段。 build.py 中的 ffibuilder.cdef("...")是声明需要包装的类型,变量以及函数的地方。这里可以直接使用 C 语言的头文件 square.h 中的内容。不过头文件中不能有预处理命令。如果有预处理命令的话,则需要手动调整声明。 |
18
lyricorpse OP @ruoyu0088 谢谢张老师! cffi 看着好简洁,我去刷一遍文档
|
19
herozem 2017-01-15 11:26:07 +08:00 1
|
20
lyricorpse OP @herozem 厉害!
|