深度学习相关的算法,都是使用python语言编写;
应用工程等基本由c++编写;想要将两者很好的结合起来,会缩短开发时间。
实际过程中,会把python的脚本编写成类的形式。然后使用C++对python类进行实例化、实例对象的成员函数调用。
1 环境配置
电脑环境:windows10、vs2015、python3.6
【python3.6】
- 安装anaconda,使用conda创建虚拟环境,并在虚拟环境中配置好自己需要的资源。
- 找到安装anaconda路径下,创建的虚拟环境的文件夹,我的电脑在默认的路径下【C:\Users\XXX\Anaconda3\envs】。其中 XXX是自己的电脑的名字。
- 将自己的虚拟环境拷贝,到创建的VS工程下。
【vs2015】
- 【新建】–>【项目】–>【Visual C++】–>【空项目】–>【修改工程位置和名称】–>【确定】.
- 将前面说的python的环境拷贝到该工程下。我的虚拟环境命名为 Pyhton_env。
- 在工程属性中配置。我的工程名为 Project1。
C/C++: $(SolutionDir)Project1\Python_env\include
链接器: $(SolutionDir)\Project1\Python_env\libs\python36.lib
dll: 将 Python_env/python36.dll 拷贝到工程路径下。
2 代码编写
2.1 方式一
2.1.1 python脚本的编写
这里举个简单的例子。定义了类,函数。一定要确保,python脚本能够正确的运行通 !!!
class Student(): def __init__(self): print("初始化Student成功============================") def SetName(self, name="test1"): print("进入Student.SetName成功=======================") print("参数:name = " + name) self._name = name print("成功完成操作:self._name = " + self._name) def PrintName(self): print("进入Student.PrintName成功=====================") print("self._name = " + self._name) def hello(): print("进入 hello 函数成功=====================") print("Hello World") def world(name): print("进入 world 函数成功=====================") print("name")
2.1.2 C++脚本的编写
【类的实例化】
- 1 打开 python脚本 :
pModule = PyImport_ImportModule("test1013")
- 2 获取 模块属性字典: 也就是将python脚本中的定义的类、函数等存入到pDict
pDict = PyModule_GetDict(pModule);
- 3 根据类名 获取类:
pClass = PyDict_GetItemString(pDict, "Student");
- 4 获取 类的构造函数:
pConstruct = PyInstanceMethod_New(pClass);
- 5 类的实例
pInstance = PyObject_CallObject(pConstruct, NULL);
【类的调用】
PyObject_CallMethod(pInstance, "SetName", "s", "1111");
#include <Python.h> #include <iostream> #include <string> #include <windows.h> int main() { // 添加的python的整个完成的环境包的路劲,必须添加。 Py_SetPythonHome(L"Python_env"); // 进行初始化 Py_Initialize(); if (Py_IsInitialized()) std::cout << "Init Success" << std::endl; PyRun_SimpleString("import sys"); // 在python初始化之后 PyRun_SimpleString("sys.path.append('./')"); // ""里面填写的是python的语言,添加根路径 PyRun_SimpleString("print(sys.version)\n"); PyObject * pModule = NULL; PyObject * pDict = NULL; PyObject * pFunc = NULL; PyObject * pClass = NULL; PyObject * pConstruct = NULL; PyObject * pInstance = NULL; //这里是要调用的文件名 pModule = PyImport_ImportModule("test1013"); //加载文件中的函数名、类名 pDict = PyModule_GetDict(pModule); if (!pDict) { printf("Cant find dictionary./n"); } // 调用类============================== { // 根据类名获取该类 pClass = PyDict_GetItemString(pDict, "Student"); if (!pClass) { printf("Can't find Student class.\n"); return -1; } // 得到类的构造函数 pConstruct = PyInstanceMethod_New(pClass); //python3的 if (!pConstruct) { printf("Can't create Student instance.\n"); return -1; } // 类的实例化 pInstance = PyObject_CallObject(pConstruct, NULL); PyObject_CallMethod(pInstance, "SetName", "s", "1111"); PyObject_CallMethod(pInstance, "PrintName", NULL, NULL); } //函数 并执行 ================================== { pFunc = PyObject_GetAttrString(pModule, "hello"); //调用函数 PyEval_CallObject(pFunc, NULL); Py_DECREF(pFunc); pFunc = PyObject_GetAttrString(pModule, "world"); PyObject_CallFunction(pFunc, "s", "zhengji"); Py_DECREF(pFunc); } //调用Py_Finalize,这个根Py_Initialize相对应的。 Py_Finalize(); return 0; }
运行结果为:
注意:PyObject_CallObject
的使用,在针对类、函数是不同的用法
- 类中使用:是对类进行实例化,并不是运行类的函数
- 函数中使用:赋予入参,运行函数
2.1.3 传参的方式
在最后补充
2.2 方式二
2.2.1 调用方式
方式一中调用python里函数,如果python工程较为复杂,就会在python工程中调用其他的python脚本时,出现问题(如找不到、报错、不运行等)。
推荐:如果调用的Python工程,可以一次性全部跑完,就可以用system("python ...")
的方式调用,会更加便捷。
import argparse parser = argparse.ArgumentParser(description='manual to this script') parser.add_argument("--function", type=str, default="0") parser.add_argument("--name", type=str, default="0") args = parser.parse_args() def hellow(): print("进入 hello 函数成功=====================") print("Hello World") def world(name): print("进入 world 函数成功=====================") print("name") if (args.function=="hellow"): hellow() else: world(args.name)
#include <Python.h> #include <iostream> #include <string> #include <windows.h> int main() { // 添加的python的整个完成的环境包的路劲,必须添加。 Py_SetPythonHome(L"Python_env"); // 进行初始化 Py_Initialize(); if (Py_IsInitialized()) std::cout << "Init Success" << std::endl; PyRun_SimpleString("import sys"); // 在python初始化之后 PyRun_SimpleString("sys.path.append('./')"); // ""里面填写的是python的语言,添加根路径 PyRun_SimpleString("print(sys.version)\n"); system("python test1013.py --function==world --name==1111"); system("python test1013.py --function==hellow"); Py_Finalize(); return 0; }
2.2.2 可能出现问题
当在VS中可正常运行工程,当把该工程打包好,被其他的工程再次进行调用发现,找不到深度学习环境,尝试了很多方法后发现:去除系统环境变量中的python环境,然后将该工程要使用的环境进行系统环境变量设置即可。(也许会有更优的方式,有网友清楚望告知)
补充
从 C++ 向 python 传参之前,需要做类型转换。转为 PyObject* 才能传入 python。
- 例如:
C++的 int 是一个整数,该值占用 8bytes(64位)的存储空间,而一个 python 的 int 实际是一个 PyObject* 指向 24bytes。
前 8个bytes是整数,代表引用次数;中间 8bytes是指向 int 类型定义的指针,最后 8bytes是才是这个 int 的值。
所以 C++ 和 Python 之间参数互相传递都需要 Python提供的 api。
假设函数
func
的输入变量有三个,分别为 一个整数(i),一个浮点数(f),一个字符串(s)PyObject* args = PyTuple_New(3); PyObject* arg1 = Py_BuildValue("i",100); //整数参数 PyObject* arg2 = Py_BuildValue("f", 3.14); //浮点数参数 PyObject* arg3 = Py_BuildValue("s", "hellow"); //字符串参数 PyTuple_SetItem(args, 0, arg1); PyTuple_SetItem(args, 0, arg2); PyTuple_SetItem(args, 0, arg3); // 以上的函数可简化为 // PyObject* args = Py_BuildValue("ifs", 100, 3.14, "hellow"); ...... PyObject *pFunc = PyObject_GetAttrString(pModule, "func"); PyEval_CallObject(pFunc, args ); Py_DECREF(pFunc);
等价于
...... PyObject *pFunc = PyObject_GetAttrString(pModule, "func"); PyObject_CallFunction(pFunc, "ifs", 100, 3.14, "hellow"); Py_DECREF(pFunc);