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模块的效果一样。下面是一个简单的例子,说明编译动态库的过程。
- 安装Cython
bash
pip install Cython
- 编写python模块
python
def hello(s):
print(s)
- 编写setup.py文件
python
from distutils.core import setup
from Cython.Build import cythonize
setup(
name = "sharedModule",
ext_modules= cythonize("sharedModule.py")
)
- 编译
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脚本的基本步骤:
- 初始化Python解释器:使用
Py_Initialize()
函数,这是调用Python脚本之前必须的步骤。 - 调用Python代码:可以使用
PyRun_SimpleString()
函数执行Python代码,或者使用PyImport_ImportModule()
函数导入Python模块。 - 获取并调用Python函数:首先,可以使用
PyObject_GetAttrString()
或者PyObject_GetAttr()
获取Python对象(例如,模块或类)的属性(例如,函数)。然后,可以使用PyObject_CallObject()
或PyObject_CallFunction()
调用获取到的Python函数。 - 处理Python函数的返回值:可以使用Python的C API提供的函数来获取和处理Python函数的返回值。
- 释放资源并关闭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