本文是用C++语言对python脚本进行调用

开发环境

电脑有NVIDIA GPU
Anaconda
pycharm
python3.12
Visual Studio 2019

1. 环境配置

1.1 python环境搭建

pytorch环境搭建见博文安装Pytorch(GPU)

1.1.1 python安装路径

如果是按本文推荐的方法进行的环境搭建,创建虚拟环境的时候就下载了python,没有指定安装路径的话,那么python的安装路径在Anaconda安装路径里,如下

C:\Users\PC\anaconda3\envs\u-net

在这里插入图片描述

1.1.2 python头文件

Python.h在include文件夹里

C:\Users\PC\anaconda3\envs\u-net\include
在这里插入图片描述

1.1.3 python库文件

.lib库文件在libs文件夹里

C:\Users\PC\anaconda3\envs\u-net\libs
在这里插入图片描述

1.2 visual studio 2019

1.2.1 添加系统环境变量

将 Python 安装目录添加到系统 PATH 环境变量,将以下两个路径添加到系统环境变量里,添加的详细步骤可参考Windows10_visual studio 2019+opencv 4.5.2里的2.1 添加系统环境变量

C:\Users\PC\anaconda3\envs\u-net
C:\Users\PC\anaconda3\envs\u-net\Scripts

1.2.2 配置属性页

为方便属性用于别的项目,可以直接为项目配置属性表,将前面python头文件路径放在包含目录里,python库文件放在库目录里,将python3.lib和python312.lib添加到依赖项里

配置属性表的详细步骤可参考Windows10_visual studio 2019+opencv 4.5.2里的2.2 在visual studio 2019里配置属性

C:\Users\PC\anaconda3\envs\u-net\include
C:\Users\PC\anaconda3\envs\u-net\libs

在这里插入图片描述

在这里插入图片描述

1.2.3 验证环境

在VS项目里新建代码文件main.cpp,代码如下

#include <Python.h>

int main()
{
    Py_Initialize();    // 初始化
    PyRun_SimpleString("print('hello python')");
    Py_Finalize();      // 释放资源
    return 0;
}

若环境搭建没有问题,此代码文件运行是没问题的,运行结果如下
在这里插入图片描述

2. C++调用python函数

在VS项目里创建script文件夹,python脚本放在此文件夹里

2.1 C++ 调用 python 函数

2.1.1 无参

无参函数调用流程:

  1. 初始化python接口(Py_Initialize)
  2. 初始化python系统文件路径(PyRun_SimpleString)
  3. 调用python文件名(PyImport_ImportModule)
  4. 获取函数对象(PyObject_GetAttrString)
  5. 调用函数对象(PyObject_CallObject)
  6. 结束python接口调用,释放资源(Py_Finalize)

script/test1.py

def say():
    print("hello python,这是test1")

修改main.cpp代码

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

int main()
{
    // 1、初始化python接口
    Py_Initialize();
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./../script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test1");
    if (module == nullptr)
    {
        std::cout <<"module not found: test1" << std::endl;
        return 1;
    }
    // 4、获取函数对象
    PyObject* func = PyObject_GetAttrString(module, "say");
    if (!func || !PyCallable_Check(func))
    {
        std::cout <<"function not found: say" << std::endl;
        return 1;
    }
    
    // 5、调用函数
    PyObject_CallObject(func, nullptr);

    // 6、结束python接口初始化
    Py_Finalize();
    return 0;
}

运行结果如下
在这里插入图片描述

2.1.2 有参

无参函数调用流程:

  1. 初始化python接口(Py_Initialize)
  2. 初始化python系统文件路径(PyRun_SimpleString)
  3. 调用python文件名(PyImport_ImportModule)
  4. 获取函数对象(PyObject_GetAttrString)
  5. 传递参数(PyTuple_New,Py_BuildValue)
  6. 调用函数对象(PyObject_CallObject)
  7. 接收函数返回值(PyArg_Parse)
  8. 结束python接口初始化(Py_Finalize)

script/test2.py

def add(a, b):
    c = a + b
    print(f"{a} + {b} = {c}")
    return c

修改main.cpp代码

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

int main()
{
    // 1、初始化python接口
    Py_Initialize();
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./../script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test2");
    if (module == nullptr)
    {
        std::cout <<"module not found: test2" << std::endl;
        return 1;
    }
    // 4、调用函数
    PyObject* func = PyObject_GetAttrString(module, "add");
    if (!func || !PyCallable_Check(func))
    {
        std::cout <<"function not found: add" << std::endl;
        return 1;
    }

    // 5、给 python 传递参数
    // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
    // 如果函数中只有一个参数时,写1就可以了
    PyObject* args = PyTuple_New(2);

    // 0:第一个参数,传入 int 类型的值 1
    PyTuple_SetItem(args, 0, Py_BuildValue("i", 1));
    // 1:第二个参数,传入 int 类型的值 2
    PyTuple_SetItem(args, 1, Py_BuildValue("i", 2));

    // 6、使用C++的python接口调用该函数
    PyObject* ret = PyObject_CallObject(func, args);

    // 7、接收python计算好的返回值
    int result;
    // i表示转换成int型变量。
    // 在这里,最需要注意的是:PyArg_Parse的最后一个参数,必须加上“&”符号
    PyArg_Parse(ret, "i", &result);
    std::cout << "return is " << result << std::endl;

    // 8、结束python接口初始化
    Py_Finalize();
    return 0;
}

运行结果如下
在这里插入图片描述

2.1.3 类

无参函数调用流程:

  1. 初始化python接口(Py_Initialize)
  2. 初始化python系统文件路径(PyRun_SimpleString)
  3. 调用python文件名(PyImport_ImportModule)
  4. 获取类(PyObject_GetAttrString)
  5. 根据类构造函数实例化对象(PyEval_CallObject)
  6. 获取实例的函数对象(PyObject_GetAttrString)
  7. 传递参数(PyTuple_New,Py_BuildValue)
  8. 调用函数对象(PyObject_CallObject)
  9. 接收函数返回值(PyArg_Parse)
  10. 结束python接口初始化(Py_Finalize)

script/test3.py

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def foo(self):
        print(f"my name is {self.name}, my age is {self.age}")

修改main.cpp代码

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

int main()
{
    // 1、初始化python接口
    Py_Initialize();
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./../script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test3");
    if (module == nullptr)
    {
        std::cout <<"module not found: test3" << std::endl;
        return 1;
    }
    // 4、获取类
    PyObject* cls = PyObject_GetAttrString(module, "Person");
    if (!cls)
    {
        std::cout <<"class not found: Person" << std::endl;
        return 1;
    }

    // 5、给类构造函数传递参数
    // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
    // 如果函数中只有一个参数时,写1就可以了
    PyObject* args = PyTuple_New(2);

    // 0:第一个参数,传入字符串类型的值 
    PyTuple_SetItem(args, 0, Py_BuildValue("s", "jack"));
    // 1:第二个参数,传入 int 类型的值 
    PyTuple_SetItem(args, 1, Py_BuildValue("i", 18));

    // 6、根据类名实例化对象
    //PyObject* obj = PyEval_CallObject(cls, args);
    PyObject* obj = PyObject_CallObject(cls, args);

    // 7、根据对象得到成员函数
    PyObject* func = PyObject_GetAttrString(obj, "foo");
    if (!func || !PyCallable_Check(func))
    {
        std::cout <<"function not found: foo" << std::endl;
        return 1;
    }
    
    // 8、使用C++的python接口调用该函数
    PyObject_CallObject(func, nullptr);
    
    // 9、结束python接口初始化
    Py_Finalize();
    return 0;
}

运行结果如下
在这里插入图片描述

常见错误

python初始化失败

ModuleNotFoundError

Fatal Python error: init fs encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named ’ encodings’
Current thread Ox0000500c (most recent cal1 first):no Python frame>

在这里插入图片描述

解决方案:
Python解释器已经启动,但它找不到自己的标准库
尽管目标是使用 u-net 虚拟环境,但从这个输出来看,C++程序实际上加载并初始化的是你的 Anaconda 基础(base)环境

以下方法可以依次尝试

方法一:移动环境变量位置

在系统环境变量那里,将用的虚拟环境的路径移动至Anaconda前
在这里插入图片描述

方法二:指定Python环境的路径

在方法一无用时,可直接指定Python环境的路径

  1. 验证环境代码

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

int main()
{
    // --- 使用 Python 3.8+ 的新初始化 API ---
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    // 明确设置 Python Home 路径到 u-net 虚拟环境
    // 这是解决问题的关键!
    PyStatus status = PyConfig_SetString(&config, &config.home, L"C:\\Users\\PC\\anaconda3\\envs\\u-net");
    if (PyStatus_Exception(status)) {
        // 如果设置失败,这是致命的,必须终止
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to set Python home to the u-net environment.");
        return 1;
    }

    // 使用我们精确配置好的信息来初始化 Python
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        // 如果初始化失败,也是致命的
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to initialize Python from config.");
        return 1;
    }

    // 初始化完成后,清理配置对象
    PyConfig_Clear(&config);

    // --- 初始化完成 ---

    // 运行Python代码
    // PyRun_SimpleString 会自动处理打印,不需要再用 std::cout
    int result = PyRun_SimpleString("print('--- hello python ---')");

    // 检查 PyRun_SimpleString 是否执行成功
    if (result != 0) {
        std::cout << "Error executing Python code." << std::endl;
        // 如果Python代码出错,打印出详细的错误信息
        PyErr_Print();
    }

    // 释放资源
    Py_Finalize();

    return 0;
}


  1. C++ 调用 python 函数(无参)
#include <iostream>
#include <Python.h>

int main()
{
    // --- 使用 Python 3.8+ 的新初始化 API ---
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    // 明确设置 Python Home 路径到你的 u-net 虚拟环境
    PyStatus status = PyConfig_SetString(&config, &config.home, L"C:\\Users\\PC\\anaconda3\\envs\\u-net");
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to set Python home to the u-net environment.");
        return 1;
    }

    // 使用我们精确配置好的信息来初始化 Python
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to initialize Python from config.");
        return 1;
    }

    // 初始化完成后,清理配置对象
    PyConfig_Clear(&config);
    // --- 初始化完成 ---

    // 检查初始化是否真的成功
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('C:\\\\Users\\\\PC\\\\Desktop\\\\SMY_dll\\\\script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test1");
    if (module == nullptr)
    {
        PyErr_Print(); // 打印详细的Python导入错误
        std::cout << "module not found: test1" << std::endl;
        return 1;
    }

    // 4、获取函数对象
    PyObject* func = PyObject_GetAttrString(module, "say");
    if (!func || !PyCallable_Check(func))
    {
        PyErr_Print(); // 打印详细的Python属性错误
        std::cout << "function not found: say" << std::endl;
        Py_DECREF(module); // 释放模块
        return 1;
    }

    // 5、调用函数
    PyObject* pRet = PyObject_CallObject(func, nullptr);
    if (pRet == nullptr) {
        PyErr_Print(); // 如果函数执行出错,打印错误
    }

    // 6、释放所有Python对象引用
    Py_XDECREF(pRet);
    Py_DECREF(func);
    Py_DECREF(module);

    // 7、结束python接口初始化
    Py_Finalize();
    return 0;
}
  1. C++ 调用 python 函数(有参)
#include <iostream>
#include <Python.h>

int main()
{
    // --- 使用 Python 3.8+ 的新初始化 API ---
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    // 明确设置 Python Home 路径到你的 u-net 虚拟环境
    PyStatus status = PyConfig_SetString(&config, &config.home, L"C:\\Users\\PC\\anaconda3\\envs\\u-net");
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to set Python home to the u-net environment.");
        return 1;
    }

    // 使用我们精确配置好的信息来初始化 Python
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to initialize Python from config.");
        return 1;
    }

    // 初始化完成后,清理配置对象
    PyConfig_Clear(&config);
    // --- 初始化完成 ---

    // 检查初始化是否真的成功
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('C:\\\\Users\\\\PC\\\\Desktop\\\\SMY_dll\\\\script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test2");
    if (module == nullptr)
    {
        std::cout << "module not found: test2" << std::endl;
        return 1;
    }
    // 4、调用函数
    PyObject* func = PyObject_GetAttrString(module, "add");
    if (!func || !PyCallable_Check(func))
    {
        std::cout << "function not found: add" << std::endl;
        return 1;
    }

    // 5、给 python 传递参数
    // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
    // 如果函数中只有一个参数时,写1就可以了
    PyObject* args = PyTuple_New(2);

    // 0:第一个参数,传入 int 类型的值 1
    PyTuple_SetItem(args, 0, Py_BuildValue("i", 1));
    // 1:第二个参数,传入 int 类型的值 2
    PyTuple_SetItem(args, 1, Py_BuildValue("i", 2));

    // 6、使用C++的python接口调用该函数
    PyObject* ret = PyObject_CallObject(func, args);

    // 7、接收python计算好的返回值
    int result;
    // i表示转换成int型变量。
    // 在这里,最需要注意的是:PyArg_Parse的最后一个参数,必须加上“&”符号
    PyArg_Parse(ret, "i", &result);
    std::cout << "return is " << result << std::endl;

    // 8、结束python接口初始化
    Py_Finalize();
    return 0;
}
  1. C++ 调用 python 类
#include <iostream>
#include <Python.h>

int main()
{
    // --- 使用 Python 3.8+ 的新初始化 API ---
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    // 明确设置 Python Home 路径到你的 u-net 虚拟环境
    PyStatus status = PyConfig_SetString(&config, &config.home, L"C:\\Users\\PC\\anaconda3\\envs\\u-net");
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to set Python home to the u-net environment.");
        return 1;
    }

    // 使用我们精确配置好的信息来初始化 Python
    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        PyConfig_Clear(&config);
        Py_FatalError("Error: Failed to initialize Python from config.");
        return 1;
    }

    // 初始化完成后,清理配置对象
    PyConfig_Clear(&config);
    // --- 初始化完成 ---

    // 检查初始化是否真的成功
    if (!Py_IsInitialized())
    {
        std::cout << "python init failed" << std::endl;
        return 1;
    }

    // 2、初始化python系统文件路径,保证可以访问到 .py文件
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('C:\\\\Users\\\\PC\\\\Desktop\\\\SMY_dll\\\\script')");

    // 3、调用python文件名,不用写后缀
    PyObject* module = PyImport_ImportModule("test3");
    if (module == nullptr)
    {
        std::cout << "module not found: test3" << std::endl;
        return 1;
    }
    // 4、获取类
    PyObject* cls = PyObject_GetAttrString(module, "Person");
    if (!cls)
    {
        std::cout << "class not found: Person" << std::endl;
        return 1;
    }

    // 5、给类构造函数传递参数
    // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
    // 如果函数中只有一个参数时,写1就可以了
    PyObject* args = PyTuple_New(2);

    // 0:第一个参数,传入字符串类型的值 
    PyTuple_SetItem(args, 0, Py_BuildValue("s", "jack"));
    // 1:第二个参数,传入 int 类型的值 
    PyTuple_SetItem(args, 1, Py_BuildValue("i", 18));

    // 6、根据类名实例化对象
    PyObject* obj = PyObject_CallObject(cls, args);

    // 7、根据对象得到成员函数
    PyObject* func = PyObject_GetAttrString(obj, "foo");
    if (!func || !PyCallable_Check(func))
    {
        std::cout << "function not found: foo" << std::endl;
        return 1;
    }

    // 8、使用C++的python接口调用该函数
    PyObject_CallObject(func, nullptr);

    // 9、结束python接口初始化
    Py_Finalize();
    return 0;
}


方法三:复制python动态链接

用方法二验证环境时没有问题,但是调用 python 函数时出错,显示python init failed

代码A(简单打印)成功了:这证明 PyConfig 初始化代码块本身是正确的。它能够成功地找到 u-net 环境,并初始化Python解释器
代码B(导入模块)失败了:它失败在了 if (!Py_IsInitialized()) 这一步,输出了 “python init failed”

项目可能发生编译时与运行时的Python环境不匹配

解决方案:统一项目配置
步骤1:检查配置属性,检查包含目录、库目录以及附加依赖项(使用绝对路径)是否正确,
步骤2:为彻底杜绝 PATH 环境变量带来的干扰,将python动态链接复制在C++程序生成的可执行文件(.exe)所在的目录

1)复制虚拟环境里的python3.dll和python312.dll文件
在这里插入图片描述
2)粘贴至C++项目文件的x64\Release或x64\Debug文件夹下
在这里插入图片描述

找不到python函数

ModuleNotFoundError: No module named test1
module not found: test1

使用绝对路径

PyRun_SimpleString("sys.path.append('C:\\\\Users\\\\PC\\\\Desktop\\\\SMY_dll\\\\script')");

参考文章

c++ 调用 python:https://www.bilibili.com/video/BV15m4y187af/
中级篇 – C++ 调用 Python 脚本:http://ai.feishu.cn/docx/VJ0hdyhWUofyw0xutfYcS1OGnvf

更多推荐