前言
用Python开发确实为我们减少了很多的约束和开发成本,从而让我们能够更加的专注于逻辑而非语法。但是,开发效率虽然提高了,但却带来了运行性能的问题。
作为一名Pythoner,我们能怎么办,我们也很慌张......
万幸的是,虽然上帝关掉了我们一扇门,但是却为我们打开了另一扇窗,因为底层是用 C语言写的,所以我们可以将一些性能损耗比较大的功能,或者模块,通过 C语言 重写,然后 import xxxx 来无缝结合。
哪怕工作中有比较少的机会自己写C扩展, 了解这块的知识,这也有利于我们更加深入的了解 Python 的运行本质。
网上的例子都是通过 ctypes 或者 setup.py 的方式实现引用和编译安装,小编在这边想试下最原始的方法~
1. 实现接口函数
接口函数是什么意思?可以简单理解成就是 Python 和 C 的对接函数,举个例子:
static PyObject *test(PyObject *self, PyObject *args){
int arg1, arg2;
if(!(PyArg_ParseTuple(args, "ii", &arg1, &arg2))){
return NULL;
}
return Py_BuildValue("i", arg1 + arg2 * 10);
}
从上述的例子中可以看到这个函数和传统意义上的 C 用法有点不同了,特别是在函数形参那边的PyObject self, PyObject args
第一个参数是 PyObject *self,这个参数是Python内部使用的,可以不用管;
第二个参数是 PyObject *args,这个参数非常重要,因为这个揽括了所有传给函数的参数。它是一个参数列表,把所有的参数都整合到。
一个 string, 因此,如果我们需要解析这些参数需要用特定的方法!我们需要用到 PyArg_ParseTuple 来解开这个扣人心弦的入口!
PyArg_ParseTuple 函数说明:
- args就是需要转换的参数;
- ii 就是参数类型的格式符号,这里代表 int init;(想了解更多类型请看官网:https://docs.python.org/2/c-a...)
- 后面的 &arg1, &arg2 就是通过参数解析提取的值,存放的地方,这有点类似 C 的 scanf;
很明显的,这三个参数,在数量上存在这一定的联系,也就是,传进去两个 int参数,那么就肯定是对应了两个 ii,然后就会对应存在 两个实际的"容器"内,这里要注意,一不小心就会 Segmentation fault
对应有解析参数的,肯定也有 C模块 值转换成 Python对象 的,那就是 Py_BuildValue。
Py_BuildValue 函数说明:
# 对比着来看
PyArg_ParseTuple(args, "ii", &arg1, &arg2) Python -> C模块
Py_BuildValue("i", arg1 + arg2 * 10); C 模块 -> Python
- 第一个参数 和 PyArg_ParseTuple 的第二个参数一样,都是格式化符号;
- 第二个参数是需要转换的参数,函数 Py_BuildValue 会把所有的返回指都组装成 tuple 给 Python
相关的官方文档:https://docs.python.org/2/c-a...
2. 定义方法列表
# 示例
static PyMethodDef testMethods[] = {
{"test", test, METH_VARARGS, "This is test"},
{NULL, NULL, 0, NULL}
};
PyMethodDef 是一个 C结构体,用来完成一个映射,也就是便于方法查找,我们把需要被外面调用的方法都记录在这表内。
PyMethodDef 结构体成员说明:
- 第一个字段:在 Python 里面使用的方法名;
- 第二个字段:C 模块内的函数名;
- 第三个字段:方法参数类型,是无参数(METH_NOARGS) , 还是有位置参数(METH_VARARGS), 还是其他等等;
- 第四个字段:方法描述,就是通过 help() 或者 doc 可以看到的;
需要注意的是,这个列表的最后必须以 {NULL, NULL, 0, NULL} 的形式来代表声明结束,也有一些大佬用 {NULL, NULL},不过个人觉得写完整也不会累到哪去, 相反会比较直观。
# PyMethodDef 结构体定义源码, 取自 Python2.7 Include/methodobject.h
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
正因为存在这样的一份记录表,Python 才能够寻找到相应的函数
同样的,如果我们想要找一个模块的 Python 函数 对应什么的 C模块方法,也能通过这地方比较粗暴得知,例如 Python 的 list
# 取自 Python2.7 object/listobject.c
static PyMethodDef list_methods[] = {
{"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, getitem_doc},
{"__reversed__",(PyCFunction)list_reversed, METH_NOARGS, reversed_doc},
{"__sizeof__", (PyCFunction)list_sizeof, METH_NOARGS, sizeof_doc},
{"append", (PyCFunction)listappend, METH_O, append_doc},
{"insert", (PyCFunction)listinsert, METH_VARARGS, insert_doc},
{"extend", (PyCFunction)listextend, METH_O, extend_doc},
{"pop", (PyCFunction)listpop, METH_VARARGS, pop_doc},
{"remove", (PyCFunction)listremove, METH_O, remove_doc},
{"index", (PyCFunction)listindex, METH_VARARGS, index_doc},
{"count", (PyCFunction)listcount, METH_O, count_doc},
{"reverse", (PyCFunction)listreverse, METH_NOARGS, reverse_doc},
{"sort", (PyCFunction)listsort, METH_VARARGS | METH_KEYWORDS, sort_doc},
{NULL, NULL} /* sentinel */
};
3. 实现初始化函数 (关键)
PyMODINIT_FUNC inittest(){
Py_InitModule("test", testMethods);
}
需要特别注意的是,这个函数名不能像上面那样,这是有规定的,必须是 init + 模块名字,比方说,我的最后编译出来的文件是 test.so, 那我的函数名就是 inittest, 这样在 Python 导入 test 模块时,才能找到这个函数并调用。
这里调用了 Py_InitModule 函数来将模块名字和映射表结合在一起。表示 test 这个模块使用 testMethods 这个映射表。
编译导出
gcc -I /usr/include/python2.7/ -fpic --shared -o test.so test.c
完整例子
test.c
#include<Python.h>
static PyObject *test(PyObject *self, PyObject *args){
int arg1, arg2;
if(!(PyArg_ParseTuple(args, "ii", &arg1, &arg2))){
return NULL;
}
return Py_BuildValue("i", arg1 + arg2 * 10);
}
static PyMethodDef testMethods[] = {
{"test", test, METH_VARARGS, "This is test"},
{NULL, NULL}
};
PyMODINIT_FUNC inittest(){
Py_InitModule("test", testMethods);
}
test.py
import test
print test.test(1, 2) # 输出 21
关于怎么快速学C/C++,可以加下小编的C/C++学习群:341+636+727,不管你是小白还是大牛,小编我都欢迎,不定期分享干货,欢迎初学和进阶中的小伙伴。
每天晚上20:00都会开直播给大家分享C/C++游戏编程学习知识和路线方法,群里会不定期更新最新的教程和学习方法,最后祝所有程序员都能够走上人生巅峰,让代码将梦想照进现实