首页 > 编程语言 >C++调用python踩坑记录

C++调用python踩坑记录

时间:2023-11-16 17:11:51浏览次数:43  
标签:调用 python 代码 Py C++ PyErr 报错

 

目录

 

0、参考文档及博客

重要的东西放前面:

用C或者C++编写模块的官方文档:
https://docs.python.org/3/extending/index.html

我配置环境时的参考博客:
https://blog.csdn.net/nanguabing007/article/details/89394541

1、环境配置步骤

这个网上有太多教程了,总结一下就是:
0、VS项目配置里,32位系统配置选x86,64位系统配置选x64,否则无法调用pyd等动态库,导致出现调用自己写的简单python代码可以,但是无法调用numpy等第三方库的问题。调用不了numpy还不会报错,只会在调用完PyImport_ImportModule函数之后返回NULL,极难调试。选Debug版就要在链接器配置里连接python36_d.lib,选release版要链接python36.lib。

1、找python源代码编译一遍,获得对应的pythonxx_d.lib文件。这里遇到过的问题主要是:(1)VS2017编译报错,显示少了个SDK。
解决方法是:用Visual Studio Installer下载所需的SDK。

2、将编译完成后出现的PcBuild->win32->python36_d.lib文件移动到相应的位置,在VS中配置好配置->链接器->输入。这里遇到的主要问题是:(1)运行调用python文件的代码时显示缺少python的动态链接文件。
解决方法是:找到python36_d.dll,移动到C:\Windows\System32文件夹下面。

3、运行调用python的C++代码。这里遇到的主要问题是:(1)调用Py_Initialize函数时,报错如下:
“Fatal Python error: Py_Initialize: unable to load the file system codec ModuleNotFoundError: No module named ‘encodings’”。
解决方法是在系统环境变量里加上这条:

网上有教程说要设置PYTHONHOME才能用,但是我试了一下,只设置PYTHONPATH就够了
注意:设置完环境变量之后要重启才能生效。

2、C++调用python的方法

代码框架:(同样来源于上面这篇博客,可用于测试环境配置成功与否)

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

using namespace std;

int main()
{
	
	Py_SetPythonHome((wchar_t*)("D:\anaconda\envs\mworks_dis")); 
	/**
	这句语句是在添加python.exe所在路径
	**/
	Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
	if (!Py_IsInitialized())
	{
		printf("初始化失败!");
		return 0;
	}
	else {
		
		PyRun_SimpleString("import sys");
		PyRun_SimpleString("sys.path.append('./')");//这一步很重要,修改Python路径


		PyObject * pModule = NULL;//声明变量
		PyObject * pFunc = NULL;// 声明变量

		pModule = PyImport_ImportModule("hello");//这里是要调用的文件名hello.py
		if (pModule == NULL)
		{
			cout << "没找到该Python文件" << endl;
		}
		else {
			pFunc = PyObject_GetAttrString(pModule, "add");//这里是要调用的函数名
			PyObject* args = Py_BuildValue("(ii)", 28, 103);//给python函数参数赋值

			PyObject* pRet = PyObject_CallObject(pFunc, args);//调用函数

			int res = 0;
			PyArg_Parse(pRet, "i", &res);//转换返回类型

			cout << "res:" << res << endl;//输出结果
		}
		Py_Finalize();//调用Py_Finalize,这个根Py_Initialize相对应的。
	}
	return 0;
}
  •  

报错处理函数

网上给的代码里对初始化python环境以及调用python文件函数的异常处理往往都是直接输出一句话,但其实这样非常不方便我们调试。(比如每次都是在调用某个python文件的时候返回NULL值,但是如果进程崩溃在python代码运行阶段,我们是无法知道问题出在哪的)
所以我们最好写一些异常处理函数,使得我们能看到python代码报出来的错误。

(1)处理方法一:PyErr_Print

在每个可能出错的地方加上这句,这样它就会把在python虚拟机里调用时发生的错误在控制台中显示出来。我们就可以根据信息来进行调试。

if (PyErr_Occurred())	PyErr_Print();

(2)处理方法二:PyErr_Fetch

这也是网上提的很多的方法,功能比上面的要自由得多。例如你并不是在写一个控制台程序,用PyErr_Print就没有地方给你输出。或者你想要做一个错误日志记录的功能,那就可以用这个方法。
网上提到这个方法的很多,但是比较少直接给出即插即用的代码块的。我找了很久才找到这个(C代码中如何得到python脚本异常时的traceback信息)。但文中代码无法即插即用,所以我在它上面作了一些小修改,得到下面的代码块。(如果使用的时候发现PyObject 这些函数都报错,说明环境没配好,建议先按照文章第一步先把python环境配好)

#pragma once
#ifndef PYTHONEXCEPTION_H
#define PYTHONEXCEPTION_H

#include <Python.h>
#include <frameobject.h>
#include <fstream>

using namespace std;

void message_error_dialog_show(char* buf)
{
	ofstream ofile;
	if(ofile)
    {
		ofile.open("error_dialog.txt", ios::out);

		ofile << buf;

		ofile.close();
    }
	return;
}

void process_python_exception()
{
	char buf[65536], *buf_p = buf;
	PyObject *type_obj, *value_obj, *traceback_obj;
	PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
	if (value_obj == NULL)
		return;

	PyObject *pstr = PyObject_Str(value_obj);

	const char* value = PyUnicode_AsUTF8(pstr);

	size_t szbuf = sizeof(buf);
	int l;
	PyCodeObject *codeobj;

	l = snprintf(buf_p, szbuf, ("Error Message:\n%s"), value);
	buf_p += l;
	szbuf -= l;

	if (traceback_obj != NULL) {
		l = snprintf(buf_p, szbuf, ("\n\nTraceback:\n"));
		buf_p += l;
		szbuf -= l;

		PyTracebackObject *traceback = (PyTracebackObject *)traceback_obj;
		for (; traceback && szbuf > 0; traceback = traceback->tb_next) {
			codeobj = traceback->tb_frame->f_code;
			l = snprintf(buf_p, szbuf, "%s: %s(# %d)\n",
				PyUnicode_AsUTF8(PyObject_Str(codeobj->co_name)),
				PyUnicode_AsUTF8(PyObject_Str(codeobj->co_filename)),
				traceback->tb_lineno);
			buf_p += l;
			szbuf -= l;
		}
	}

	message_error_dialog_show(buf);

	Py_XDECREF(type_obj);
	Py_XDECREF(value_obj);
	Py_XDECREF(traceback_obj);
}

#endif // !PYTHONEXCEPTION_H
  •  

把这段代码粘贴到需要进行异常处理的地方就好。
在这里插入图片描述

2.5、终极解决方案

如果实在被调用过程搞烦了,还有一个权宜之计,直接用system(“python xxxxxxxx.py”)来调。

3、踩坑记录

按照前面的步骤应该能解决绝大多数问题了。下面这一章是我在某个特定项目中踩的坑,不一定能帮到读者。

(1)python第三方库调用出错

我在开发时调用到了两份python代码,一份只调用了os、zipfile等这些自带的库,顺利调用,另一份调用了numpy、pandas这类第三方库,然后就报错。

排查出来是两个原因导致的:python第三方库不完整(或损坏)、电脑上有加密软件之类的。

第一种情况是python第三方库不完整(或损坏),报错信息里会写某个库里缺少dll(得先配好上文第二步的PyErr_Fetch代码才能看到是哪个库的问题),这种情况就直接卸载这个库再重装就好。我开发的时候把pandas、numpy、lxml库都重装了一遍之后就好了。

第二种情况是电脑上有加密软件之类的,这种情况应该在实验室或者公司里很常见,具体原因是某些加密软件的加密策略中没有考虑到VS也有调用.py文件的需求,所以通过VS调不动.py脚本文件,也就是说VS运行python代码的时候,打开的是乱码,那自然没法进行编译运行。解决方法是更新一下加密软件的加密策略,或者把要调用的.py文件编译成dll和lib,又或者用下面的代码编译成.pyc文件(编译完之后记得把生成的.pyc文件名中多出来的.cpython36这些删掉)。

import py_compile

py_compile.compile(r'E:\xxxx.py')

(2)python模块环境太大

一开始开发的时候由于不知道python环境中哪些是要用到的,哪些是不需要的,所以把整个1.8G的python文件夹打包到模块中交付给别人(觉得自己好憨啊…)

后来经过一番查阅,发现其实只需要以下文件就可以让C++成功调用python代码了。

这里面每个文件夹的作用在文章的第一部分已经写了,一些是项目依赖项,一些是系统环境变量要求的。两个dll是备用了,如果接收的人电脑里没有这两个dll的话你可以直接给他。

(3)软件无法调用该调用了python代码的模块

我单独开发的时候,是写一个控制台demo来进行测试的,测试时可以顺利调用python,但是交付集成到别的软件中时却无法调用。这种情况建议把python代码编译成dll和lib再用。具体原因我也没懂,但确实凑效了,读者遇到这种问题可以试一试这种方法。

至于其他的坑,现在回头想想都是因为没有用上PyErr_Fetch看不到报错信息,导致无从下手调试。用上PyErr_Fetch之后,对着报错信息来处理,很多问题都迎刃而解了。

传参进python虚拟机的方式

https://www.freesion.com/article/1509186861/

 

标签:调用,python,代码,Py,C++,PyErr,报错
From: https://www.cnblogs.com/lidabo/p/17836764.html

相关文章

  • 微服务 在 Java 代码中发送 http 请求(跨服务远程调用)
    1.注册RestTemplate对象到Spring容器中(Bean的注入只能放在配置类里,而启动类本身就是配置类)@SpringBootApplicationpublicclassOrderServiceApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderServiceApplication.class,......
  • 《流畅的Python》 读书笔记 第8章_对象引用、可变性和垃圾回收
    第8章_对象引用、可变性和垃圾回收本章的主题是对象与对象名称之间的区别。名称不是对象,而是单独的东西name='wuxianfeng'#name是对象名称'wuxianfeng'是个str对象variablesarelabels,notboxes变量是标注,而不是盒子引用式变量的名称解释本章还会讨论标识......
  • python生成 时间戳和日期格式
    1.获取当前日期要获取当前日期,我们可以使用datetime模块中的datetime类的now()方法。下面是获取当前日期的代码示例:importdatetimecurrent_date=datetime.datetime.now().date()print("当前日期:",current_date)#运行以上代码,输出的结果类似于:当前日期:2022-01-01#获取时......
  • C#调用C++动态库接口函数和回调函数方法 后续
    声明回调委托,C#的委托可以实现C#调用C++的回调,操作函数以后的回调//定义委托,CallingConvention.StdCall可以,CallingConvention.Cdecl不行,参考https://www.it1352.com/1792610.html//[UnmanagedFunctionPointer(CallingConvention.Cdecl)]//不需要要添加该句话,具体参考//htt......
  • SAP ABAP调用REST服务
    就是调用为外部HTTP接口 zcl_json=>deserialize因为版本问题 自定义的json转换函数 根据自己的版本使用对应函数就好reportztest25.data:urltypestring,"接口地址gv_json_intypestring,"输入参数(账号密码啥的)jso......
  • Python的txt文本操作-读、写
    ✅作者简介:热爱科研的算法开发者,Python、Matlab项目可交流、沟通、学习。......
  • python调用ffmpeg循环播放一个文件夹内的视频,如果播放中断了,下次继续播放可以从上次播
    importosimportsubprocessdefplay_videos_in_folder(folder_path):#获取所有视频文件files=[os.path.join(folder_path,f)forfinos.listdir(folder_path)iff.endswith(('.mp4','.mkv'))]idx=0#视频文件索引whileTrue:......
  • SAP调用外部的REST服务 http_communication_failure Connection to partner timed
    SAP中主动调用外部的REST服务时候, 因为传输的数据量比较大, 所以报Connectiontopartnertimedoutafter60 这一错误,原因之一可能是Tcode-SMICM ->转到->服务设置保活和处理超时时间,秒为单位,可以更具自己的需求进行设置。......
  • C#调用C++动态库接口函数和回调函数方法
    这篇文章主要介绍了C#调用C++动态库接口函数和回调函数方法,通过C++端编写接口展开内容,文章介绍详细具有一定的参考价值,需要的小伙伴可以参考一下需求: 当前C已经写好了一个动态库,完成了产品开发需求,C#需要调用C编写的动态库DLL接口,开发出完整的软件,DLL动态库里包含了普通接口函......
  • Python 在PDF中生成水印
    前言在PDF中插入水印是比较常用的一种功能。一般在生成比较重要的,或者需要注明版权、作者的文档时使用比较多。这里我将分享一个通过python代码为PDF文档添加水印的办法(包括文本水印和图像水印)。这种方法也适用于批量添加水印的情况。所需工具:这个方法将用到以下程序和组件V......