首页 > 编程语言 >C++调用Python3实战,和PyImport_ImportModule返回NULL问题解决

C++调用Python3实战,和PyImport_ImportModule返回NULL问题解决

时间:2023-11-16 17:34:18浏览次数:42  
标签:调用 python ImportModule C++ Python usr GIL PyImport

Linux C++调用Python3

入门

准备

以下面的目录结构演示如何在Linux C/C++调用python3。

|--hello.py
|--main.cpp
|--CMakeLists.txt

 

  • hello.py:python的脚本,里面有2个函数
  • main.cpp:c++函数
  • CMakeLists.txt:Cmake文件,生成makefile

python脚本

示例python脚本hello.py内容如下,有2个函数:

def add(a,b):
    return a+b

def get_name(first):
    return "your name is {} alice".format(first)

 

c++调用python3

#include <iostream>
#include <Python.h>

using namespace std;

const int kError = -1;
const int kSuccess = 0;

int pythonInit() {
    //初始化python
    Py_Initialize();
    int ret = Py_IsInitialized();
    if (ret == 0) {
        cout << "ocr_card_init Py_Initialize error" << endl;
        return kError;
    }
    return kSuccess;
}

void pythonCleanup() {
    Py_Finalize();
}

PyObject *pythonImportModule(const char *pyDir, const char *name) {
    //引入当前路径,否则下面模块不能正常导入
    char tempPath[256] = {};
    sprintf(tempPath, "sys.path.append('%s')", pyDir);
    PyRun_SimpleString("import sys");
    //PyRun_SimpleString("sys.path.append('./')");
    PyRun_SimpleString(tempPath);
    PyRun_SimpleString("print('curr sys.path', sys.path)");

    //引入模块, hello.py
    PyObject *module = PyImport_ImportModule(name);
    if (module == nullptr) {
        PyErr_Print(); // print stack
        cout << "PyImport_ImportModule 'hello.py' not found" << endl;
        return nullptr;
    }

    return module;
}

int callPythonAdd(PyObject *module, int a, int b) {
    //获取模块字典属性
    PyObject *pDict = PyModule_GetDict(module);
    if (pDict == nullptr) {
        PyErr_Print();
        std::cout << "PyModule_GetDict error" << std::endl;
        return kError;
    }

    //直接获取模块中的函数
    PyObject *addFunc = PyDict_GetItemString(pDict, "add");
    if (addFunc == nullptr) {
        std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
        return kError;
    }

    // 构造python 函数入参, 接收2
    // see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
    PyObject *pArg = Py_BuildValue("(i,i)", a, b);

    //调用函数,并得到python类型的返回值
    PyObject *result = PyEval_CallObject(addFunc, pArg);

    int ret = 0;
    //将python类型的返回值转换为c/c++类型
    PyArg_Parse(result, "i", &ret);
    return ret;
}

int callPythonGetName(PyObject *module, std::string firstName, std::string &outName) {
    //获取模块字典属性
    PyObject *pDict = PyModule_GetDict(module);
    if (pDict == nullptr) {
        PyErr_Print();
        std::cout << "PyModule_GetDict error" << std::endl;
        return kError;
    }

    //直接获取模块中的函数
    PyObject *addFunc = PyDict_GetItemString(pDict, "get_name");
    if (addFunc == nullptr) {
        std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
        return kError;
    }

    // 构造python 函数入参, 接收2
    // see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
    PyObject *pArg = Py_BuildValue("(s)", firstName.c_str());

    //调用函数,并得到python类型的返回值
    PyObject *result = PyEval_CallObject(addFunc, pArg);

    char *name = nullptr;
    //将python类型的返回值转换为c/c++类型
    PyArg_Parse(result, "s", &name);

    char tempStr[256] = {};
    int strLen = strlen(name);
    if (strLen > 256) {
        return kError;
    }
    strcpy(tempStr, name);
    outName = tempStr;

    return kSuccess;
}

通过C/C++ 封装好之后,即可使用了:

int main() {
    pythonInit();

    //int argc = 1;
    //wchar_t *argv[1] = {L"ocrTest"};
    //PySys_SetArgv(argc, argv);

    //直接运行python代码
    PyRun_SimpleString("print('----------hello Python form C/C++')");

    PyObject *helloModule = pythonImportModule("../", "hello");
    if (helloModule == nullptr) {
        return -1;
    }

    // call python add function
    int result = callPythonAdd(helloModule, 1, 3);
    cout << "1 + 3 = " << result << endl;

    // call python get_name function
    std::string fullName;
    callPythonGetName(helloModule, "summer", fullName);
    cout << fullName << endl;

    pythonCleanup();
}

执行后输出:

----------hello Python form C/C++
curr sys.path ['/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/home/tengqing/.local/lib/python3.6/site-packages', '/usr/local/lib64/python3.6/site-packages', '/usr/local/lib/python3.6/site-packages', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages', '../']
1 + 3 = 4
your name is summer alice

Process finished with exit code 0

 

依赖安装

环境是CentOS 7,gcc是4.8.5默认的,这里要注意是使用python2还是3。

$ sudo yum install libstdc++ #  version `CXXABI_1.3.8' not found 问题
$ sudo yum install binutils  # 升级ld版本
$ sudo yum install python3         # python3 --version
$ sudo yum install python3-devel   # c/c++调用python需要python.so

 

C/C++调用Python需要借助 Python.hPython.so,即上面 python3-devel 执行后能在 /usr/include/usr/lib64下找到。

CMakeLists

所以,在CMakeLists.txt中,我们需要配置python3-devel包:

cmake_minimum_required(VERSION 3.19)
project(test)

set(CMAKE_CXX_STANDARD 14)

# 这里需要配置python3的头文件和python.so的库位置,在centos7中,通过如下方式查看
#[tengqing@localhost card]$ ls /usr/include/python
#python2.7/  python3.6m/
set(PYTHON3_VERSION 3.6m)
if (UNIX)
    set(PYTHON3_INCLUDE /usr/include/python${PYTHON3_VERSION}/)
    set(PYTHON3_LIBS /usr/bin/python3.6m-x86_64-config)
endif()

# 指定生成可执行程序
add_executable(test main.cpp)

# 头文件搜索目录
target_include_directories(${PROJECT_NAME}
        PUBLIC
        ${PYTHON3_INCLUDE} # 这样外部就能找到头文件所在目录了
        )
# 动态库搜索目录
target_link_directories(${PROJECT_NAME}
        PUBLIC
        ${PYTHON3_LIBS})
# 链接动态库
target_link_libraries(${PROJECT_NAME}
        PRIVATE
        python${PYTHON3_VERSION})

 

多线程环境下调用Python3

GIL简介

来自:https://zhuanlan.zhihu.com/p/146874652

在使用python解释器时,要注意GIL(全局解释锁)的工作原理以及对性能的影响。GIL保证在任意时刻只有一个线程在解释器中运行。在多线程环境中,python解释器工作原理如下:

  1. 设置GIL
  2. 切换到一个线程去运行
  3. 运行:
    a. 指定数量的字节码指令,或者
    b. 线程主动让出控制(可以调用time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁GIL
  6. 再次重复以上所有步骤

对性能的影响: 假如有一段两个线程的python代码,运行在一个两核CPU上。由于GIL的存在,两个线程无法真正并行执行,CPU占用率总是低于50%。

GIL是一个历史遗留问题,导致CPython多线程不能利用多个CPU内核的计算能力。为了利用多核,通常使用多进程的方法,或是通过Python调用C代码,由C来实现多线程。

注意,当在C/C++创建的线程中调用Python时,GIL需要通过函数PyGILState_Ensure()和PyGILState_Release()手动获取、释放。 C++

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

/* Perform Python actions here. */
result = CallSomeFunction();
/* evaluate result or handle exception */

/* Release the thread. No Python API allowed beyond this point. */
PyGILState_Release(gstate);

C++封装GIL和正确的使用方式

封装GIL

有点类似mutex互斥锁,为了方便使用,我们封装成自动释放的锁,如下:

// https://docs.python.org/zh-cn/3/c-api/init.html?highlight=pygilstate_ensure
class PyThreadStateLock {
public:
    PyThreadStateLock() {
        cout << "----------------PyGILState_Ensure start" << endl;
        state = PyGILState_Ensure();
        cout << "----------------PyGILState_Ensure end" << endl;
    }

    ~PyThreadStateLock() {
        cout << "----------------PyGILState_Release start" << endl;
        PyGILState_Release(state);
        cout << "----------------PyGILState_Release end" << endl;
    }

private:
    PyGILState_STATE state;
};
初始化python3修改

在主线程中初始化python后,需要调用 PyEval_SaveThread() 释放全局解释器。

static PyThreadState *g_mainThreadState = nullptr;

int ocr_card_init(){
	//初始化python
	Py_Initialize();
	
	// 初始化线程支持
	PyEval_InitThreads();
	
	// release the GIL, store thread state, set the current thread state to NULL
	g_mainThreadState = PyEval_SaveThread();
}
每次调用python申请GIL

每次调用python函数之前,获取一下全局解释器即可。

int ocr_card_detect(const char *image_path, char *out_buffer, int *buffer_len) {
    PyThreadStateLock autoLock;
    
    // call python function
    // ...
   
    return kSuccess;
}
退出前释放

程序结束前释放,别忘记了恢复线程状态,获取全局解释器。

void ocr_card_cleanup() {
    if (g_mainThreadState != nullptr) {
        //re-acquire the GIL (re-initialize the current thread state)
        PyEval_RestoreThread(g_mainThreadState);
        Py_Finalize();
    }
}

遇到问题

version `CXXABI_1.3.8’ not found 问题

升级一下libstdc++库的版本即可。

sudo yum install libstdc++ #  version `CXXABI_1.3.8' not found 问题

PyImport_ImportModule返回NULL

原因一:找不到*.py脚本,或者没有引入脚本所在路径

找不到python脚本,或者目录不正确,引入一下即可:

PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
原因二:没有安装依赖

执行之前,需要安装一下相关依赖,比如:

$ pip3 install -r requirements.txt
原因三:python脚本有异常

没有正确安装,或者依赖出错,我们可以通过堆栈查看具体的错误,然后解决。

//引入模块, hello.py
PyObject *module = PyImport_ImportModule(name);
if (module == nullptr) {
    // print stack, 看看具体是什么错误
    PyErr_Print(); 
    cout << "PyImport_ImportModule 'hello.py' not found" << endl;
    return nullptr;
}

参考

 

标签:调用,python,ImportModule,C++,Python,usr,GIL,PyImport
From: https://www.cnblogs.com/lidabo/p/17836821.html

相关文章

  • 基于pybind11实现C++程序中调用Python脚本增加C++程序扩展性
     文章目录前言一、pybind11与Python环境配置二、C++环境配置三、C++调用Python交互代码四、C++调用PythonDemo完整源码 前言Windows平台,在实际C++项目开发中,结合pybind11库,让python成为C++的脚本语言,可以大大提高C++程序的可扩展性,大大提高开发效率,特别......
  • C++通过pybind11调用Python 实现transpose
    在某些场合需要在C++实现类似numpy的numpy.transpose(a,axes)功能,但是很多库如NumCpp都没有提供这样的方法,只有二维矩阵的转置,没法进行多维矩阵任意维度的转换。比较简单的想法就是利用numpy现有的功能,在c++代码里面通过调用python来调用Numpy的transpose。直接调用Python提......
  • C++调用python踩坑记录
     目录0、参考文档及博客1、环境配置步骤2、C++调用python的方法代码框架:(同样来源于上面这篇博客,可用于测试环境配置成功与否)报错处理函数(1)处理方法一:PyErr_Print(2)处理方法二:PyErr_Fetch2.5、终极解决方案3、踩坑记录(1)python第三方库调用出错(2)python模块环......
  • C#调用C++动态库接口函数和回调函数方法 后续
    声明回调委托,C#的委托可以实现C#调用C++的回调,操作函数以后的回调//定义委托,CallingConvention.StdCall可以,CallingConvention.Cdecl不行,参考https://www.it1352.com/1792610.html//[UnmanagedFunctionPointer(CallingConvention.Cdecl)]//不需要要添加该句话,具体参考//htt......
  • C#调用C++动态库接口函数和回调函数方法
    这篇文章主要介绍了C#调用C++动态库接口函数和回调函数方法,通过C++端编写接口展开内容,文章介绍详细具有一定的参考价值,需要的小伙伴可以参考一下需求: 当前C已经写好了一个动态库,完成了产品开发需求,C#需要调用C编写的动态库DLL接口,开发出完整的软件,DLL动态库里包含了普通接口函......
  • 【每日例题】 蓝桥杯 c++ 冶炼金属
    冶炼金属题目小蓝有一个神奇的炉子用于将普通金属О冶炼成为一种特殊金属X。这个炉子有一个称作转换率的属性V,V是一个正整数,这意味着消耗V个普通金属О恰好可以冶炼出一个特殊金属X,当普通金属О的数目不足V时,无法继续冶炼。现在给出了Ⅳ条冶炼记录,每条记录中包含两个整数A和B,这......
  • C++实现YoloV7目标识别与实例分割推理
    前言1.简介7月份,由YOLOV4的原班人马Chien-YaoWang、AlexeyBochkovskiy和Hong-YuanMarkLiao推出的YoloV7,应该是目前开源的目标检测算法最好之一了,它在在5FPS到160FPS范围内的速度和精度达到了新的高度,优于YOLOR、YOLOX、Scaled-YOLOv4、YOLOv5、DETR等多种目标检测器......
  • C/C++ 实现获取硬盘序列号
    获取硬盘的序列号、型号和固件版本号,此类功能通常用于做硬盘绑定或硬件验证操作,通过使用WindowsAPI的DeviceIoControl函数与物理硬盘驱动程序进行通信,发送ATA命令来获取硬盘的信息。以下是该程序的主要功能和流程:定义常量IDE_ATAPI_IDENTIFY和IDE_ATA_IDENTIFY分别表示读取......
  • c++ AI实战手册-c++ 20(1)
    目录hello,worldhello,worldimport<iostream>;usingnamespacestd;intmain(){cout<<"Helloworld!"<<endl;return0;}g++-c-fmodules-ts-xc++-system-header-std=c++20iostream#g++-fmodules-ts-std=c++20......
  • c++ AI 实战手册(3)-gtk(1)
    目录gtk概述hello,worldgtk概述GTK是一个小部件工具包。由GTK创建的每个用户界面都由小部件组成。这是在C中使用GObject实现的,这是一个面向对象的C框架。[]小部件被组织在一个层次结构中。窗口小部件是主容器。然后,通过向窗口中添加按钮、下拉菜单、输入字段和其他小部件来构建......