实践将C++函数暴露给Python

目标

在之前的博客中我实践在C++中调用Python函数,即C++能够“知道”Python定义的东西。而本篇将实践让Python能够“知道”C++定义的东西,即:将C++函数暴露给Python。

我从《如何实现 C/C++ 与 Python 的通信? - 知乎》中获得了些提示。
不过代码主要参考了《1. 使用 C 或 C++ 扩展 Python — Python 3.9.1 文档》。

(执行python文件方面从《【Python有坑系列】报错NameError: name ‘execfile’ is not defined_小白兔de窝-CSDN博客》获得了提示)

实践

首先,我的C++函数定义如下:

int MyCppFunction(int x) 
{
     
	return x * 2 + 3;
}

随后,将其封装为Python的格式:

static PyObject * MyCppFunction_python(PyObject *self, PyObject *args)
{
     
	//输入的参数
	int parm;

	//将python参数以int为格式解析,写入parm
	if (!PyArg_ParseTuple(args, "i", &parm))
		return NULL;

	//调用原生的C++函数
	int result = MyCppFunction(parm);
	
	//将结果转变为Python格式
	return PyLong_FromLong(result);
}

然后,定义一个表,将上述的函数放入其中:

static PyMethodDef Methods[] = 
{
     
	{
     
		"MyCppFunction",		//函数名字
		MyCppFunction_python,	//封装为Python格式的C++函数
		METH_VARARGS,			// Combination of METH_xxx flags, which mostly describe the args expected by the C func
		NULL,					// The __doc__ attribute, or NULL 
	},
	
	{
     
		NULL, NULL, 0, NULL		//全是0的元素,表中最后的元素一定要是它,扮演一个“哨兵”的角色来标志表何时结束
	},
};

注意结尾应是{NULL, NULL, 0, NULL}(感觉就像是字符串的结尾是\0作为标志)。


然后定义一个叫yaksue的模块,并填入上表

static struct PyModuleDef yaksue_module =
{
     
	PyModuleDef_HEAD_INIT,
	"yaksue",				//模块名字
	NULL,					//模块文档,可以是NULL
	-1,						// size of per-interpreter state of the module, or -1 if the module keeps state in global variables. 
	Methods					//方法表
};

随后,定义一个用来初始化这个模块的函数:

PyMODINIT_FUNC
PyInit_yaksue_module(void)
{
     
	return PyModule_Create(&yaksue_module);
}

下面,写一个Python脚本(D:/Temp/script.py),调用函数:

# 导入模块
import yaksue

# 测试打印出模块
print(yaksue)

#测试打印函数定义
print(yaksue.MyCppFunction)

# 测试调用C++函数
print(yaksue.MyCppFunction(6))

然后,在C++的main函数中,创建yaksue这个内建模块,然后执行 D:/Temp/script.py 这个文件:

int main()
{
     
	//添加一个内建模块
	//这一步需要放在Py_Initialize之前
	if (PyImport_AppendInittab("yaksue", PyInit_yaksue_module) == -1)
	{
     
		fprintf(stderr, "Error: could not extend in-built modules table\n");
		exit(1);
	}

	//设定Python解释器的名字
	Py_SetProgramName(L"YaksueTestPython");

	//初始化Python解释器。
	//这一步是必须的,如果出错,则会有一个 fatal error
	Py_Initialize();

	//执行 D:/Temp/script.py
	PyRun_SimpleString("exec(open('D:/Temp/script.py', encoding = 'utf-8').read())");
	

	Py_Finalize();
	
	return 0;
}

输出:
实践将C++函数暴露给Python_第1张图片
可以看到调用结果符合预期。

完整代码

C++:

#include 

#include

//我的C++函数:
int MyCppFunction(int x) 
{
     
	return x * 2 + 3;
}

//将我的C++函数封装成Python的格式
static PyObject * MyCppFunction_python(PyObject *self, PyObject *args)
{
     
	//输入的参数
	int parm;

	//将python参数以int为格式解析,写入parm
	if (!PyArg_ParseTuple(args, "i", &parm))
		return NULL;

	//调用原生的C++函数
	int result = MyCppFunction(parm);
	
	//将结果转变为Python格式
	return PyLong_FromLong(result);
}

//记录所有方法的表
static PyMethodDef Methods[] = 
{
     
	{
     
		"MyCppFunction",		//函数名字
		MyCppFunction_python,	//封装为Python格式的C++函数
		METH_VARARGS,			// Combination of METH_xxx flags, which mostly describe the args expected by the C func
		NULL,					// The __doc__ attribute, or NULL 
	},
	
	{
     
		NULL, NULL, 0, NULL		//全是0的元素,表中最后的元素一定要是它,扮演一个“哨兵”的角色来标志表何时结束
	},
};


//yaksue模块定义
static struct PyModuleDef yaksue_module =
{
     
	PyModuleDef_HEAD_INIT,
	"yaksue",				//模块名字
	NULL,					//模块文档,可以是NULL
	-1,						// size of per-interpreter state of the module, or -1 if the module keeps state in global variables. 
	Methods					//方法表
};

PyMODINIT_FUNC
PyInit_yaksue_module(void)
{
     
	return PyModule_Create(&yaksue_module);
}

int main()
{
     
	//添加一个内建模块
	//这一步需要放在Py_Initialize之前
	if (PyImport_AppendInittab("yaksue", PyInit_yaksue_module) == -1)
	{
     
		fprintf(stderr, "Error: could not extend in-built modules table\n");
		exit(1);
	}

	//设定Python解释器的名字
	Py_SetProgramName(L"YaksueTestPython");

	//初始化Python解释器。
	//这一步是必须的,如果出错,则会有一个 fatal error
	Py_Initialize();

	//执行 D:/Temp/script.py
	PyRun_SimpleString("exec(open('D:/Temp/script.py', encoding = 'utf-8').read())");
	

	Py_Finalize();
	
	return 0;
}

Python:

# 导入模块
import yaksue

# 测试打印出模块
print(yaksue)

#测试打印函数定义
print(yaksue.MyCppFunction)

# 测试调用C++函数
print(yaksue.MyCppFunction(6))

延伸问题

  • 当前程序里是用C++创建的内建模块,并调用python脚本。而另一种让python能调用C++的方法是将C++编译为dll(或者说.pyd)作为模块,待试验。
  • 当前的方式使用Python原生支持的方式,其实还有更方便的库,正如《如何实现 C/C++ 与 Python 的通信? - 知乎》提到的SWIG,待试验。

你可能感兴趣的:(Python,python)