(Python/C API)使用C/C++调用Python

文章目录

  • 一、(Python/C API)简介
  • 1.1、四种常用方法:Python/C API、Cython、Boost.Python、SWIG
  • 1.2、extern "C":在 C++ 中,用于声明一个 C 代码函数
  • 二、(Python/C API)环境配置
  • 2.1、python安装 + Visual Studio安装
  • 2.2、python环境配置
  • 2.3、Visual Studio环境配置(执行 python 代码)
  • 三、(Python/C API)项目实战
  • 3.1、在C/C++中,调用python接口(无参函数 + 有参函数 + 类)
  • 3.1.1、新建.py文件(call_python.py)
  • 3.1.2、新建.cpp文件(main.cpp) —— #include <Python.h>
  • 3.2、在C/C++中,执行python文件
  • 3.2.1、新建.py文件(call_python.py)
  • 3.2.2、新建.cpp文件(main.cpp) —— #include <Python.h>
  • 四、(Python/C API)函数详解
  • 4.1、算法流程
  • 4.2、解析参数与构建
  • 4.2.1、构建(数值与字符串)对象:Py_BuildValue()
  • 4.2.1、构建(元组)对象:PyTuple_New() + PyTuple_SetItem()
  • 一、(Python/C API)简介

    API(Application Programming Interface,应用程序编程接口)

    Python/C API:是 Python 提供的 C 语言的 API 库。实现在 C/C++ 中嵌入 Python 代码,包括C/C++ 调用 Python 函数、C/C++ 操作 Python 对象、双向通信等等。

  • 使用Python/C API,需要在 C/C++ 源文件中,添加头文件:#include <Python.h>
  • Python 是一种非常强大的胶水语言,可以灵活的嵌入到 C++ 和 Java 等主流语言中。
  • 优点:加速 Python 程序、扩展 C/C++ 模块
  • 缺点:集成 C/C++ 和 Python,增加了代码复杂性和维护成本。
  • 官网资料:Python 解释器的扩展与嵌入

  • (1)使用 C/C++ 扩展 CPython —— 调用 C 例程
  • (2)使用 C/C++ 嵌入 CPython —— 调用 Python 例程
  • Python/C API 参考手册:描述了扩展编写的一般原则,但没有详细记录API函数。
  • 1.1、四种常用方法:Python/C API、Cython、Boost.Python、SWIG

  • Python:是一种高级编程语言。具有简单易学、动态类型、面向对象等特点,被广泛用于Web开发、科学计算、人工智能等领域。
  • CPython:是 Python 的默认解释器,由 C 语言实现CPython 解释器执行 Python 代码,并将其转换为底层的字节码以运行。
  • Python/C API
  • Cython:是一种编程语言,它是 Python 的一个超集用于将 Python 代码转换为 C/C++ 扩展模块,然后嵌入 Python 中使用。
  • 加速 Python 程序不支持在 C/C++ 中调用 Python 代码。
  • Cython官网
  • Cython 实战篇
  • Boost.Python:支持在Python中调用C++的类和方法,也可以让C++调用Python的类和方法。
  • Boost.Python官网
  • Boost::Python的安装与调用教程
  • SWIG(Simplified Wrapper and Interface Generator):是一个接口编译器,它将 C 和 C++ 编写的程序与 Perl、Python、Ruby 和 Tcl 等脚本语言连接起来。它的工作原理是获取 C/C++ 头文件中的声明,并使用它们生成脚本语言访问底层 C/C++ 代码所需的包装器代码。
  • SWIG官网
  • SWIG学习记录(一)SWIG基础
  • 1.2、extern “C”:在 C++ 中,用于声明一个 C 代码函数

    在 C++ 中,extern "C" 关键字用于声明一个 C 代码函数它告诉编译器将指定的 C++ 函数名,按照 C 语言的规则进行编译和链接,即不对函数名进行名称修饰(Name Mangling)。

  • C++ 的编译器会对函数名进行名称修饰(Name Mangling),以支持函数重载和命名空间
  • C 语言不支持函数名称修饰。
  • 因此,若在 C++ 中声明的函数需要被 C 调用,就需要将 C++函数 声明为 extern “C”,以防止名称修饰。
  • // 方法一、用于声明多个 C 代码函数 ————— 使用了 C++ 中的 extern "C" 语法块,将函数声明包裹在了花括号{}内
    extern "C" 
    {
        void my_c_function(int arg);
    }
    
    // 方法二、用于声明单个 C 代码函数 —————— 将 extern "C" 关键字放在了函数声明之前
    extern "C" void my_c_function(int arg);			
    

    二、(Python/C API)环境配置

    基本环境:
    (1)win10
    (2)Visual Studio2022
    (3)python3.10(安装在Anaconda下)

    2.1、python安装 + Visual Studio安装

    (1)Anaconda – python安装;(2)Visual Studio安装;

    2.2、python环境配置

    安装python成功后,需要添加两个系统环境变量:PYTHONHOMEPYTHONPATH

    操作步骤:电脑 + 属性 + 高级系统设置 + 环境变量 + 系统变量 + 新建

  • 注意事项:添加后,若需要调用其他虚拟环境
  • 报错提示: ImportError: cannot import name 'text_encoding' from 'io' (unknown location)
  • 原因分析:python版本冲突
  • 解决方法:
  • 删除环境变量 PYTHONHOME 和 PYTHONPATH
  • 测试结果:(已解决)将D:\Anaconda\envs环境下的虚拟环境剪切到其他路径,然后再剪切回来。
  • 若不添加,后续将无法正常运行,而只能得到以下结果(报错提示):
  • 2.3、Visual Studio环境配置(执行 python 代码)

    11、新建项目:打开VS + 文件 + 新建 + 项目 + 空项目 + 创建
    22、环境配置:选中新建项目 + 属性

    33、新建文件:项目 + 源文件 + 添加 + 新建项 + main.cpp
    44、添加代码:在main.cpp文件中添加以下源码

    #include <Python.h>  // Python/C API头文件,它包含了与 Python 交互所需的函数和数据结构的声明。
    
    int main()
    {
        Py_Initialize();  								// 初始化 Python 解释器
        PyRun_SimpleString("print('hello python')");  	// 执行 Python 代码
        Py_Finalize(); 							 		// 关闭 Python 解释器,以释放资源。
        return 0;  // 表示程序正常结束,并返回值 0。
    }
    

    55、输出结果:在main.cpp文件中添加以下源码

    报错提示:LNK1104: 无法打开文件“python310_d.lib” 。
    解决方法:

  • (1)定位文件:Anaconda\include\pyconfig.h
  • (2)修改文件:
  • 11、打开文件:pyconfig.h
  • 22、搜索信息:Ctrl + F + comment(lib
  • 33、修改代码:pragma comment(lib,"python310_d.lib") 修改为 pragma comment(lib,"python310.lib")
  • 三、(Python/C API)项目实战

    3.1、在C/C++中,调用python接口(无参函数 + 有参函数 + 类)

    c++调用python接口(实战)
    c++调用python接口(函数总结)

    3.1.1、新建.py文件(call_python.py)

    def hello():
        print("hello python to C++")
    
    def add(a, b):
        return a + b
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def introduce(self):
            print(f"my name is {self.name}, my age is {self.age}.")
    
    

    3.1.2、新建.cpp文件(main.cpp) —— #include <Python.h>

    #include <iostream>
    #include <Python.h>  // Python/C API头文件
    
    int main()
    {
    	// (1)初始化 python 解释器
    	Py_Initialize();
    	if (!Py_IsInitialized())  // 检查 Python 是否成功初始化
    	{
    		std::cout << "python init failed" << std::endl;
    		return 1;
    	}
    	// -----------------------------------------------------------------------------------------
    	// (2)调用.py文件
    	// 
    	// 文件路径 C:\Users\Administrator\Desktop\Project1\call_python.py
    	// -----------------------------------------------------------------------------------------
    	// 2.1、执行python语句(导入依赖库)
    	PyRun_SimpleString("import sys");
    	// 2.2、执行python语句(添加.py文件路径)
    	PyRun_SimpleString("sys.path.append('C:/Users/Administrator/Desktop/Project1')");  // 备注:\是转义符
    	// 2.3、导入.py文件(只包含文件名,而不包含后缀名)
    	PyObject* module = PyImport_ImportModule("call_python");  
    	if (module == nullptr) // 检查模块是否成功导入
    	{
    		std::cout << "module not found: call_python.py" << std::endl;
    		return 1;
    	}
    	else
    	{
    		// -----------------------------------------------------------------------------------------
    		// (3)C++调用python(无参函数)
    		// -----------------------------------------------------------------------------------------
    		// 3.1、获取.py文件中函数:hello()
    		PyObject* hello_func = PyObject_GetAttrString(module, "hello");
    		if (!hello_func || !PyCallable_Check(hello_func))  // 检查对象是否成功获取
    		{
    			std::cout << "function not found: hello()" << std::endl;
    			return 1;
    		}
    		// 3.2、调用.py文件中函数:hello(),并传入参数为 nullptr(空指针)
    		PyObject_CallObject(hello_func, nullptr);
    
    		// -----------------------------------------------------------------------------------------
    		// (4)C++调用python(有参函数 + 返回值)
    		// -----------------------------------------------------------------------------------------
    		// 4.1、获取.py文件中函数:add()
    		PyObject* add_func = PyObject_GetAttrString(module, "add");
    		if (!add_func || !PyCallable_Check(add_func))
    		{
    			std::cout << "function not found: add()" << std::endl;
    			return 1;
    		}
    
    		// 4.2、设置参数
    		PyObject* args = PyTuple_New(2);  // 创建一个包含两个元素的空元组
    		PyTuple_SetItem(args, 0, Py_BuildValue("i", 1));  // 在元组的第一个位置0,传入"i"整数 1(int类型)
    		PyTuple_SetItem(args, 1, Py_BuildValue("i", 2));  // 在元组的第二个位置1,传入"i"整数 2(int类型)
    
    		// 4.3、调用.py文件中函数:add(),并传入参数为 args
    		PyObject* ret = PyObject_CallObject(add_func, args);
    
    		// 4.4、保存结果
    		int result;  // 定义一个整数变量(result)
    		PyArg_Parse(ret, "i", &result);  // 解析 Python 函数的返回值(ret);将其解析为整数并保存到(result)变量中
    		std::cout << "return value = " << result << std::endl;
    
    		// -----------------------------------------------------------------------------------------
    		// (5)C++调用python类
    		// -----------------------------------------------------------------------------------------
    		// 5.1、获取.py文件中Person类的对象
    		PyObject* cls = PyObject_GetAttrString(module, "Person");
    		if (!cls)
    		{
    			std::cout << "class not found: Person" << std::endl;
    			return 1;
    		}
    
    		// 5.2、设置参数
    		PyObject* introduce_args = PyTuple_New(2);  // 创建一个包含两个元素的空元组
    		PyTuple_SetItem(introduce_args, 0, Py_BuildValue("s", "jack"));  // 在元组的第一个位置0,传入"s"字符串 "jack"(string类型)
    		PyTuple_SetItem(introduce_args, 1, Py_BuildValue("i", 18));	// 在元组的第二个位置0,传入"i"整数 1(int类型)
    
    		// 5.3、类的实例化并传递参数为 args
    		PyObject* introduce_obj = PyObject_CallObject(cls, introduce_args);
    
    		// 5.4、获取实例中的成员函数"introduce"
    		PyObject* introduce_func = PyObject_GetAttrString(introduce_obj, "introduce");
    		if (!introduce_func || !PyCallable_Check(introduce_func))
    		{
    			std::cout << "function not found: introduce" << std::endl;
    			return 1;
    		}
    
    		// 5.5、调用.py文件中Person类的函数"introduce",不传入参数
    		PyObject_CallObject(introduce_func, nullptr);
    	}
    	Py_Finalize();  // 关闭 Python 解释器
    	return 0;  // 返回 0 表示程序正常结束
    }
    
    

    3.2、在C/C++中,执行python文件

    c++调用python文件:直接执行文件,但无法与Python代码进行交互。

    3.2.1、新建.py文件(call_python.py)

    def hello():
        print("hello python to C++")
    
    def add(a, b):
        return a + b
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def introduce(self):
            print(f"my name is {self.name}, my age is {self.age}.")
    
    
    if __name__ == '__main__':
        print("True")
        hello()
        print(add(1, 2))
        Person("me", 1).introduce()
    
    

    3.2.2、新建.cpp文件(main.cpp) —— #include <Python.h>

    #include <iostream>
    #include <Python.h>  // Python/C API头文件
    
    int main()
    {
    	// (1)初始化 python 解释器
    	Py_Initialize();
    	if (!Py_IsInitialized())  // 检查 Python 是否成功初始化
    	{
    		std::cout << "python init failed" << std::endl;
    		return 1;
    	}
    
    	// (2)执行 python 脚本
    	int result = system("python C:/Users/Administrator/Desktop/Project1/call_python.py");
    	if (result != 0)  // 检查命令执行结果
    	{
    		std::cout << "Command execution failed with result: " << result << std::endl;
    		return 1;
    	}
    	std::cout << "Command exited successfully" << std::endl;  // 输出命令执行结果
    
    	Py_Finalize();  // 关闭 Python 解释器
    	return 0;  // 返回 0 表示程序正常结束
    }
    // -----------------------------------------------------------------------------------------
    // 函数功能:system() 是一个 C 标准库函数,用于在程序中执行命令行指令。
    // 函数说明:system(const char* command);
    // 输入参数:command是一个包含要执行的命令的字符串。
    // 			
    //			举例:system("python xxx.py") 其中:xxx.py是一个Python脚本文件。
    // -----------------------------------------------------------------------------------------
    
    

    四、(Python/C API)函数详解

    4.1、算法流程

    c++调用python(本质上):在 c++ 中启动了一个 python 解释器,由解释器对 python 相关的代码进行执行,执行完毕后释放资源,达到调用目的。

    """###################################################################################
    # (1)初始化 python 解释器:			Py_Initialize();
    # (2)执行 python 语句:			PyRun_SimpleString();
    # (3)导入 .py 文件:			    PyImport_ImportModule();
    #
    # (4)获取 .py 文件中的函数或类:   PyObject_GetAttrString();
    #         (4.1)调用类(无参):         PyObject_CallObject(_func, nullptr);
    #         (4.2)调用无参函数:           PyObject_CallObject(_func, nullptr);
    #         (4.3)调用有参函数:           PyObject_CallObject(_func, args);
    #                   (4.3.1)构建参数 方法一:     Py_BuildValue()
    #                   (4.3.2)构建参数 方法二:     PyTuple_New() + PyTuple_SetItem()
    #
    # (5)结束 Python 解释器:			Py_Finalize();
    ###################################################################################"""
    

    4.2、解析参数与构建

    4.2.1、构建(数值与字符串)对象:Py_BuildValue()

    PyObject *Py_BuildValue(const char *format, …)

    """###################################################################################
    # 函数功能:创建新值。
    # 函数说明:PyObject *Py_BuildValue(const char *format, ...)
    # 输入参数:
    #			format      格式字符串,用于指定要构建的 Python 对象的类型。
    # 			(1)若格式字符串为空,则返回None;
    #			(1)若包含一个格式单元,则返回该格式单元描述的任何对象。
    #			(2)若包含两个或多个格式单元,则创建元组。
    #
    #				"s":构建字符串对象				接受一个 C 字符串作为参数。
    #				"i":构建整数对象				接受一个 C 整数作为参数。
    #				"f":构建浮点数对象				接受一个 C 浮点数作为参数。
    #				"O":构建任意 Python 对象的引用	接受一个 PyObject* 类型的指针作为参数。
    # 		  
    #         Py_BuildValue("")                                           None
    #         Py_BuildValue("i", 123)                                     123
    #         Py_BuildValue("iii", 123, 456, 789)                         (123, 456, 789)
    #         Py_BuildValue("s", "hello")                                 'hello'
    #         Py_BuildValue("y", "hello")                                 b'hello'
    #         Py_BuildValue("ss", "hello", "world")                       ('hello', 'world')
    #         Py_BuildValue("s#", "hello", 4)                             'hell'
    #         Py_BuildValue("y#", "hello", 4)                             b'hell'
    #         Py_BuildValue("()")                                         ()
    #         Py_BuildValue("(i)", 123)                                   (123,)
    #         Py_BuildValue("(ii)", 123, 456)                             (123, 456)
    #         Py_BuildValue("(i,i)", 123, 456)                            (123, 456)
    #         Py_BuildValue("[i,i]", 123, 456)                            [123, 456]
    #         Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456)          {'abc': 123, 'def': 456}
    #         Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))
    ###################################################################################"""
    

    4.2.1、构建(元组)对象:PyTuple_New() + PyTuple_SetItem()

    Python/C API:数值 + 列表 + 元组 + 字典

    """###################################################
    # 函数功能:创建一个新的元组对象。
    # 函数说明:PyTuple_New(size)
    # 输入参数:
    #             size 表示创建元组的大小(即元素个数)。
    # 输出参数:
    #             若创建成功,则返回一个指向新创建的元组对象的指针;
    #             若创建失败,则返回 NULL。
    ###################################################
    # 函数功能:设置元组对象中指定索引位置的元素。
    # 函数说明:PyTuple_SetItem(tuple, index, item):
    # 输入参数:
    #             tuple     是指向元组对象的指针。
    #             index     是要设置的元素的索引位置,从 0 开始。
    #             item      是要设置的元素对象,可以是任何 Python 对象。
    # 输出参数:     若设置成功,则返回 0;
    #              若设置失败,则返回 -1。
    ###################################################################################
    # 举例理解:
    #             PyObject* myTuple = PyTuple_New(3)				        创建一个包含三个元素的空元组
    #             PyTuple_SetItem(myTuple, 0, Py_BuildValue("i", 10))		在元组的第 0(1/2)个位置,设置一个整数元素
    ###################################################################################"""
    

    作者:胖墩会武术

    物联沃分享整理
    物联沃-IOTWORD物联网 » (Python/C API)使用C/C++调用Python

    发表回复