python作为一门动态语言,语法的灵活性和强大的模块支持使得开发效率大大提升,传统C/C++程序员可以借助python来实现业务逻辑来减少开发成本。而另一方面,python灵活的语言特性带来的代价是性能的降低,在一些密集计算型任务面前显得力不从心,但这个问题可以由C/C++来解决,将对性能要求较高的部分用C语言来实现即可, 而且对于一些加密解密算法,还可以保持源码的私密性。而本文正是针对两者的双剑合璧,对C/C++与python相互调用的讲解。
笔者以斐波那契递归函数为例,将模块名定为cai,在python代码中用import cai
导入该模块
首先笔者创建了一个cai.c和cai.h文件, 源码如下
cai.h
#ifndef __CAI_H__
#define __CAI_H__
#include <stdio.h>
int fib(int n);
int test(void);
#endif
cai.c
#include "cai.h"
// 斐波那契递归函数
int fib(int n)
{
return n <= 2 ? 1 : fib(n - 1) + fib(n - 2);
}
// 功能测试
//int main(void)
int test(void)
{
for (int i = 1; i <= 10; i++)
printf("fib[%d] = %d\n", i, fib(i));
return 0;
}
编译测试,保证代码的正确性。
实现包裹,主要分4步:
创建cai_wrapper.c文件,源码如下
cai_wrapper.c
#include "cai.h"
// 包含Python.h头文件
#include <Python.h>
// 为fib函数增加包裹函数
static PyObject *cai_fib(PyObject *self, PyObject *args)
{
// 用于存储从python传过来的参数
int n;
// 将python传递过来的int类型值转为C语言可识别的int类型值
// "i"表示python和C/C++之间int的转换,其他的转换可参见本程序代码后面的表格
// PyArg_ParseTuple为可变参函数,如果需要包裹的函数为多个参数,参考示例:
// PyArg_ParseTuple(args,"lls", &k, &l, &s); /* Two longs and a string */
if (!PyArg_ParseTuple(args, "i", &n)) {
// 如果转化失败,则返回空
return NULL;
}
// 将fib函数返回值的int类型转为python可识别的int类型
return (PyObject *)Py_BuildValue("i", fib(n));
}
// 为test函数增加包裹函数
static PyObject* cai_test(PyObject *self, PyObject *args)
{
test();
return (PyObject *)Py_BuildValue("");
}
// 为模块增加一个形如PyMethod caiMethods[]的数组,
// 每一个数组元素包含了在python中调用的函数名、对应的包裹函数名、METH_VARARGS常量,
// METH_VARARGS表示参数以tuple形式传递,
// 数组最后用两个NULL来表示函数信息列表的结束
static PyMethodDef caiMethods[] = {
{ "fib", cai_fib, METH_VARARGS },
{ "test", cai_test, METH_VARARGS },
{ NULL, NULL },
};
// 模块初始化
void initcai(void)
{
// Py_InitModule函数的第一个参数为模块名, 第二个参数为上面定义的数组名
Py_InitModule("cai", caiMethods);
}
Python与C/C++之间数据转化:
Format Code | Python Type | C/C++ Type |
---|---|---|
s | str | char * |
z | str/None | char*/NULL |
i | int | int |
l | long | long |
c | str | char |
d | float | double |
D | complex | Py_Complex * |
O | (any) | PyObject * |
S | str | PyStringObject |
创建setup.py文件,编译相关工作由setup.py来完成。
#coding=utf-8
from distutils.core import setup, Extension
# 模块名
MOD = 'cai'
# source参数为所有C/C++源代码的文件名列表
setup(name=MOD, ext_modules=[Extension(MOD, sources=['cai.c', 'cai_wrapper.c'])])
运行setup.py
python setup.py build
成功执行后,当前的目录结构为
会发现在build/lib.linux-x86_64-2.7下生成了cai.so动态链接库, 可以将模块安装到全局python模块路径下,使用如下命令
python setup.py install
或者进入到build/lib.linux-x86_64-2.7目录下来使用该模块
OK,!至此就完成了Python调用C/C++编写的模块!
由于C++没有大数类,不支持大数乘法,而python先天的优势拥有大数算法,所以这里笔者以大数乘法为例,
创建了一个calc.py文件, 源码如下
calc.py
#coding=utf-8
def mul(a, b):
val = long(a) * long(b)
return str(val)
if __name__ == '__main__':
print(mul('1234567890000', '9876543210000'))
利用python来实现大数乘法, 供c++使用,笔者创建了test.cpp,源码如下
test.cpp
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include "Python.h"
int main(void)
{
// 初始化python
Py_Initialize();
// 初始化python系统环境路径
// PyRun_SimpleString函数将传入的字符串直接当作python代码运行,成功返回0,失败返回-1
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
// 加载python文件 这里载入的是calc.py,但无需加后缀
PyObject *pModule = PyImport_ImportModule("calc");
// 加载函数 这里载入的是mul函数
PyObject *pFunc = PyObject_GetAttrString(pModule, "mul");
// 将参数压栈
// 函数调用的参数传递均是以元组的形式打包的, 这里的2表示参数个数为2
PyObject *pArgs = PyTuple_New(2);
// 0表示元组的0下标,表示第一个参数,
// Py_BuildValue用来将c/c++可识别的类型转为python可识别的,
// Py_BuildValue的第一个参数为转换格式代码,第二个参数为传递给python函数的参数
PyTuple_SetItem(pArgs, 0, Py_BuildValue("s", "1234567890000"));
// 第二个参数
PyTuple_SetItem(pArgs, 1, Py_BuildValue("s", "9876543210000"));
// 调用函数,并接收返回值
PyObject *pReturn = PyEval_CallObject(pFunc, pArgs);
// 将python返回来的参数类型转为c/c++可识别的类型
char *val;
PyArg_Parse(pReturn, "z", &val);
// 关闭python
Py_Finalize();
std::cout << std::string(val) << std::endl;
}
执行以下命令生成可执行文件
g++ test.cpp -I/usr/include/python2.7 -lpython2.7 -o test.o
执行./test.o
就完成了在c++中对python的调用
本文作者: Ifan Tsai (菜菜)
本文链接: https://cloud.tencent.com/developer/article/2164598
版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!