C/C++调用python

python环境

通过如下的命令,可以获取到当前python环境下对应的路径。比如程序编译需要指定对应的头文件、库文件路径,链接阶段需要指定需要链接哪些库。

bash 复制代码
# 说明python3-config支持哪些选项
$ python3-config 
Usage: /home/yangye/miniconda3/bin/python3-config --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir|--embed

$ python3-config --prefix
/home/yangye/miniconda3
# 对应的头文件路径
$ python3-config --includes
-I/home/yangye/miniconda3/include/python3.10 -I/home/yangye/miniconda3/include/python3.10
# 对于python3.8 以下通过--libs可以获取对应的链接库名称
$ python3-config --libs
 -lcrypt -lpthread -ldl  -lutil -lm -lm 
# python3.8以上需要加上--embed
$ python3-config --libs --embed
-lpython3.10 -lcrypt -lpthread -ldl  -lutil -lm -lm 

$ python3-config --cflags
-I/home/yangye/miniconda3/include/python3.10 -I/home/yangye/miniconda3/include/python3.10  -Wno-unused-result -Wsign-compare -march=nocona -mtune=haswell -ftree-vectorize -fPIC -fstack-protector-strong -fno-plt -O3 -ffunction-sections -pipe -isystem /home/yangye/miniconda3/include -fdebug-prefix-map=/croot/python-split_1679423815169/work=/usr/local/src/conda/python-3.10.10 -fdebug-prefix-map=/home/yangye/miniconda3=/usr/local/src/conda-prefix -fuse-linker-plugin -ffat-lto-objects -flto-partition=none -flto -DNDEBUG -fwrapv -O3 -Wall
# 对应的库文件路径
$ python3-config --ldflags
-L/home/yangye/miniconda3/lib/python3.10/config-3.10-x86_64-linux-gnu -L/home/yangye/miniconda3/lib  -lcrypt -lpthread -ldl  -lutil -lm -lm 

$ python3-config --extension-suffix

.cpython-310-x86_64-linux-gnu.so
$ python3-config --abiflags

$ python3-config --configdir
/home/yangye/miniconda3/lib/python3.10/config-3.10-x86_64-linux-gnu

编译动态库

python支持将一个模块编译成动态库,然后对动态库进行调用,与import模块的效果一样。下面是一个简单的例子,说明编译动态库的过程。

  1. 安装Cython
bash 复制代码
 pip install Cython
  1. 编写python模块
python 复制代码
def hello(s):
    print(s)
  1. 编写setup.py文件
python 复制代码
from distutils.core import setup
from Cython.Build import cythonize

setup(
    name = "sharedModule",
    ext_modules= cythonize("sharedModule.py")
)
  1. 编译
python 复制代码
python setup.py build_ext --inplace

C++调用Python

本文重点是介绍如何使用C/C++调用python程序,具体实现代码参考了如下文章。C语言调用Python脚本的原理主要基于Python提供的C API。Python的C API允许C语言程序调用Python解释器,并进行交互操作,如执行Python代码、调用Python函数等。
C++使用Python/C API_c++ 调用api_Eliza_Her的博客-CSDN博客

CMake

下面是一个CMake编译对应的CMakeFiles.txt文件内容,具体可以执行定义的命令替换相应的路径。

cmake 复制代码
cmake_minimum_required(VERSION 3.19)
project(cppCallPythonFunc)
 
#set(CMAKE_CXX_STANDARD 14)
# python3-config --includes
include_directories(
   "/home/xxxx/miniconda3/include/python3.10"
)
 
#在控制台运行python3.6-config --ldflags 取得参数
#-L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
 
# 添加动态库的位置
LINK_DIRECTORIES("/home/xxxx/miniconda3/lib/python3.10/config-3.10-x86_64-linux-gnu" "/home/xxxx/miniconda3/lib")
# LINK_DIRECTORIES("/usr/lib")
 
add_executable(main main.cpp)
target_link_libraries(main -lpython3.10 -lcrypt -lpthread -ldl  -lutil -lm )

调用python

C/C++调用Python脚本的基本步骤:

  1. 初始化Python解释器:使用Py_Initialize()函数,这是调用Python脚本之前必须的步骤。
  2. 调用Python代码:可以使用PyRun_SimpleString()函数执行Python代码,或者使用PyImport_ImportModule()函数导入Python模块。
  3. 获取并调用Python函数:首先,可以使用PyObject_GetAttrString()或者PyObject_GetAttr()获取Python对象(例如,模块或类)的属性(例如,函数)。然后,可以使用PyObject_CallObject()PyObject_CallFunction()调用获取到的Python函数。
  4. 处理Python函数的返回值:可以使用Python的C API提供的函数来获取和处理Python函数的返回值。
  5. 释放资源并关闭Python解释器:使用Py_Finalize()函数。

直接运行python代码段

cpp 复制代码
#include <Python.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) 
{
    // 初始化python解释器.C/C++中调用Python之前必须先初始化解释器
    Py_Initialize();
    if(Py_IsInitialized())
    {
        std::cout << "python translator initialized." << std::endl;
    }
    // 执行一个简单的执行python脚本命令
    PyRun_SimpleString("print('hello world')\n");
    // 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化
    // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
    Py_Finalize();
    return 0;
}

直接运行python脚本

cpp 复制代码
#include <Python.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) 
{
    // 初始化python解释器.C/C++中调用Python之前必须先初始化解释器
    Py_Initialize();
    if(Py_IsInitialized())
    {
        std::cout << "python translator initialized." << std::endl;
    }
	// 运行python脚本
	PyRun_SimpleString("import sys");
	PyRun_SimpleString("sys.path.append('..')");
	// 输入python脚本文件名
	string filename = "../simple_run.py";
	PyObject* obj = Py_BuildValue("s", filename.c_str());
	FILE* file = _Py_fopen_obj(obj, "r+");
	if (file != NULL)
	{
		PyRun_SimpleFile(file, filename.c_str());
	}

    // 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化
    // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
    Py_Finalize();
    return 0;
}

导入python模块,调用python函数

解析参数并构建值变量
如何将二维数组从C语言传递到Python?
C调用Python(传递数字、字符串、list数组(一维、二维),结构体)_c++与python 传递数组-CSDN博客
https://docs.python.org/3/c-api/index.html

以下代码是Py_BuildValue函数构建不同类型的数据,作为python函数的输入,可以做一个参考。

cpp 复制代码
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) '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))

以下是一个简单的python模块,定义了三种函数,无参数,简单类型的参数和list参数输入。

cpp 复制代码
import time
import numpy as np
import torch
import cv2

def print_time():
    print(f"now {time.ctime()}")


def add_num(a, b):
    print(np.__version__)
    print(torch.__version__)
    print(cv2.__version__)
    return a + b

def test_lst(lst):
    print(lst)

下面是具体的C++代码实现,导入文件的模块,然后调用其中的函数内容。测试list有好多坑呀。

cpp 复制代码
#include <Python.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) 
{
    // 初始化python解释器.C/C++中调用Python之前必须先初始化解释器
    Py_Initialize();
    if(Py_IsInitialized())
    {
        std::cout << "python translator initialized." << std::endl;
    }

	PyRun_SimpleString("import sys");
	PyRun_SimpleString("sys.path.append('..')");

	PyObject* pModule = NULL;
	PyObject* pFunc = NULL;
	PyObject* args = NULL;

	//import模块
	pModule = PyImport_ImportModule("simple_module");//模块文件名
    //找不到模块则报错
	if (pModule == nullptr) {
		PyErr_Print();
		Py_Finalize();
		return 0;
	}

 	//1. 调用不带参数的函数 Hello()
	pFunc = PyObject_GetAttrString(pModule, "print_time");//函数名
	PyObject_CallFunction(pFunc, NULL);//调用函数

	//2. 调用带参数的函数 Add(a, b)
	pFunc = PyObject_GetAttrString(pModule, "add_num");
	args = Py_BuildValue("(ii)", 123, 456); //设置传入Add的参数
	PyObject* pRet = PyObject_CallObject(pFunc, args); //pRet = Add(123, 456)

	// 3. 解析返回值
  	int ans = 0;
	PyArg_Parse(pRet, "i", &ans);	//返回类型转换
	printf("Return C++: ans = %d\n", ans);

	// 测试一维数组
	double CArray[] = {1.2, 4.5, 6.7, 8.9, 1.5, 0.5};
	PyObject *PyList  = PyList_New(6);
	PyObject *ArgList = PyTuple_New(1);

    for(int i = 0; i < PyList_Size(PyList); i++)
        PyList_SetItem(PyList,i, PyFloat_FromDouble(CArray[i]));//给PyList对象的每个元素赋值

    PyTuple_SetItem(ArgList, 0, PyList);//将PyList对象放入PyTuple对象中
	pFunc = PyObject_GetAttrString(pModule, "test_lst");//函数名
    PyObject_CallObject(pFunc, ArgList);//调用函数,完成传递
    // 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化
    // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
    Py_Finalize();
    return 0;
}

调用python类

python中类的使用比较普遍,下面重点介绍一下如何导入python类,并访问对应的属性和方法,具体参考了如下文章。
【精选】C调用python类的正确方法-CSDN博客

下面的代码定义了一个类,并导入了深度学习中常用的库作为测试,并测试了list作为输入的情况。

cpp 复制代码
import numpy as np
import torch
import cv2

class myClass:
    welcome = "Hello from Python class attribute"
 
    def hello(self):
        print(np.__version__)
        print(torch.__version__)
        print(cv2.__version__)
        print("Hello from Python class method")
 
    def minus(self, a, b):
        print(str(a) + " - " + str(b) + " = " + str(a-b))
        return a-b
    
    def print_lst(self, lst):
        print(lst)

以下是对应的C++代码,具体可以参考注释。

cpp 复制代码
#include <Python.h>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) 
{
    // 初始化python解释器.C/C++中调用Python之前必须先初始化解释器
    Py_Initialize();
    if(Py_IsInitialized())
    {
        std::cout << "python translator initialized." << std::endl;
    }

	PyRun_SimpleString("import sys");
	PyRun_SimpleString("sys.path.append('..')");

	PyObject* pModule = NULL;
	PyObject* pFunc = NULL;
	PyObject* args = NULL;

	//import模块
	pModule = PyImport_ImportModule("simple_class");//模块文件名
    //找不到模块则报错
	if (pModule == nullptr) {
		PyErr_Print();
		Py_Finalize();
		return 0;
	}

	// 模块的字典列表
	PyObject* pDict = PyModule_GetDict(pModule);
	if (!pDict) {
		PyErr_Print();
		Py_Finalize();
		return 0;
	}
 
	//从字典中获取myClass类
	PyObject* pClassCalc = PyDict_GetItemString(pDict, "myClass");
	if (!pClassCalc) {
		PyErr_Print();
		Py_Finalize();
		return 0;
	}

	// 获取构造函数
	PyObject* pConstruct = PyInstanceMethod_New(pClassCalc);
	if (!pConstruct) {
		PyErr_Print();
		Py_Finalize();
		return 0;
	}
    // 调用构造函数 构建对象
	PyObject* pIns = PyObject_CallObject(pConstruct,nullptr);

    //获取pIns实例的属性,转换成字符串并输出
	PyObject* obj2 = PyObject_GetAttrString(pIns, "welcome");
	PyObject* str = PyUnicode_AsEncodedString(obj2, "utf-8", "strict");
	char* result = PyBytes_AsString(str);
    printf("%s\n", result);
 
    //如果属性是int型,可用下面这句转换属性:
    //int qwq;
    //PyArg_Parse(obj2, "i", &qwq);
	// args = Py_BuildValue("(ii)", 123, 456); //设置传入Add的参数
	args = PyTuple_New(2);
	PyTuple_SET_ITEM(args, 0, Py_BuildValue("i", 1));
	PyTuple_SET_ITEM(args, 1, Py_BuildValue("i", 2));
    
	PyObject_CallMethod(pIns, "print_lst", "(O)", args);
	
	//调用无参数Python方法
	PyObject_CallMethod(pIns, "hello", nullptr);

    //调用多参数Python方法
    PyObject* pRet = PyObject_CallMethod(pIns, "minus","(i,i)", 12, 22);
	if (!pRet)
	{
		PyErr_Print();
		Py_Finalize();
		return 0;
	}
 
	int res = 0;
	PyArg_Parse(pRet, "i", &res);//转换返回类型
 
	printf("Return C++: ans = %d\n", res);

    // 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化
    // 并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
    Py_Finalize();
    return 0;
}

从动态库中导入python模块

参考第二小节:编译动态库,得到动态库,将其拷贝到main.cpp的同级目录下,此时可以以动态库的形式加载python模块,后续使用和之前文件一样。

项目的整体文件结构如下:

cpp 复制代码
.
├── [ 761]  CMakeLists.txt
├── [ 13K]  main
├── [2.3K]  main.cpp
├── [ 57K]  simple_class.cpython-310-x86_64-linux-gnu.so
├── [ 102]  simple_module.py
├── [ 113]  simple_run.py
└── [4.0K]  test_shared
    ├── [4.0K]  build
    │   ├── [4.0K]  lib.linux-x86_64-cpython-310
    │   │   └── [ 57K]  simple_class.cpython-310-x86_64-linux-gnu.so
    │   └── [4.0K]  temp.linux-x86_64-cpython-310
    │       └── [ 56K]  simple_class.o
    ├── [ 150]  setup.py
    ├── [269K]  simple_class.c
    ├── [ 57K]  simple_class.cpython-310-x86_64-linux-gnu.so
    └── [ 241]  simple_class.py
相关推荐
qq_529025298 分钟前
Torch.gather
python·深度学习·机器学习
数据小爬虫@8 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
yuyanjingtao36 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
Cachel wood40 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
終不似少年遊*1 小时前
pyecharts
python·信息可视化·数据分析·学习笔记·pyecharts·使用技巧
Python之栈1 小时前
【无标题】
数据库·python·mysql
闻缺陷则喜何志丹1 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满1 小时前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http