(Python/C API)使用C/C++调用Python
文章目录
一、(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 APICython:是一种编程语言,它是 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成功后,需要添加两个系统环境变量:PYTHONHOME和PYTHONPATH。
操作步骤:
电脑 + 属性 + 高级系统设置 + 环境变量 + 系统变量 + 新建
注意事项:添加后,若需要调用其他虚拟环境 报错提示: 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.h22、 搜索信息:Ctrl + F + comment(lib33、 修改代码: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)个位置,设置一个整数元素
###################################################################################"""
作者:胖墩会武术



