在学习或者搭建demo时可能会需要使用python调用自己使用c++编写的接口。实际上很多python模块底层都是用c++实现的,比如 cv2、pytorch 等。
那么如何使用python调用自己编写的c++接口呢?原理很简单
- 在c++代码中使用特殊技术将c++接口封装成模块并暴露出去;
- 将c++代码打包成.so文件;
- python加载这个.so文件并导入模块;
- 像调用python函数一样调用c++接口;
原理简单,操作起来更简单,下面简单介绍一种搭建python调用c++工程的方法。
1 安装依赖
pybind11
pybind11
主要用于创建现有C++代码的Python绑定,是一个轻量级的仅头文件库。 源码见:https://github.com/pybind/pybind11
pybind11
可以直接使用pip
工具安装
pip3 install pybind11
2 编写c++代码
编写c++代码包括两部分:
- 编写c++接口和业务代码;
- 使用宏
PYBIND11_MODULE
注册模块,并向外部暴露;
举个例子,新建cpp文件Sample.cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
int AddNums(int left, int right)
{
return left + right;
}
// 注册需要暴露给 python 的函数
PYBIND11_MODULE(mathDemo, m)
{
m.def("AddNums", &AddNums, "Call Add Sums Function");
}
3 编译c++代码并安装.so文件
编译c++代码并生成.so文件的方式有很多种,可以使用 gcc/g++ 命令,也可以使用 cmake 工具。本文使用的是python的包管理工具集setuptools
,在工程根目录下新建脚本setup.py
from setuptools import setup, Extension
mathDemo = Extension(
"mathDemo",
sources=["Sample.cpp"],
include_dirs=["/opt/homebrew/lib/python3.10/site-packages/pybind11/include"], # Note: 替换成 pybind11 头文件实际安装的位置
language="c++",
extra_compile_args=["-std=c++11", "-g"],
)
setup(
name='mathDemo',
version='0.1',
ext_modules=[mathDemo],
options={"install": {"install_lib": "lib"}}, # Note: 可选, 没指定 options["install"] 的话会安装到系统路径下,不妨碍使用
install_requires=['pybind11>=2.6'],
)
写好setup.py
后执行以下命令即可编译c++代码、生成so并自动安装到指定路径
python3 setup.py install
注:
setup.py
中的 options 是可选项,options[“install”]用于指定.so文件的安装位置。默认安装到系统路径下(可以使用pip3 freeze
命令查到)
4 编写python代码调用c++接口
调用c++接口与调用python模块的方法是一样的,编写python代码调用c++代码即可
import sys
sys.path.append("lib/mathDemo-0.1-py3.10-macosx-12-arm64.egg") # Note: 可选,如果选择安装在系统路径下,这里不需要指定 .so 路径
import mathDemo
value = mathDemo.AddNums(1, 3) # 调用 c++ 的 AddNums 接口
print(f"sum: {value}")
一些补充
上面的例子很简单,入参和返回值都是基本数据类型,实际上pybind11
支持更复杂的数据类型,例如常见的std容器std::map
、std::vector
、std::set
以及字符串std::string
和自定义结构体。
std::string
可以直接与python的str转换std::vector
和std::map
可以直接与python的内置list和dict转换- 支持自定义结构体
- 引用类型可以直接使用
- 使用std容器传参
举个例子
c++代码
#include <string>
#include <vector>
#include <map>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
std::vector<std::map<std::string, int>> MovePoints(std::vector<const std::map<std::string, int>>& pots, std::map<std::string, int> vect)
{
std::vector<std::map<std::string, int>> resPots(pots.size());
for (int index = 0; index < pots.size(); index++)
{
resPots[index]["x"] = pots[index].at("x") + offset.at("x");
resPots[index]["y"] = pots[index].at("y") + offset.at("y");
}
return resPots;
}
// 注册 MovePoints
PYBIND11_MODULE(demo, m)
{
m.def("MovePoints", &MovePoints, "Call Move Points Function");
}
python代码
pots = [{"x": 1, "y": 4},
{"x": 2, "y": 5},
{"x": 3, "y": 6},
{"x": 4, "y": 7},
{"x": 5, "y": 8}]
offset = {"x": -1, "y": 1}
resPots = demo.MovePoints(pots, offset)
print(f"{resPots}")
- 使用自定义类型传参
举个例子
c++代码
#include <pybind11/pybind11.h>
class MyClass {
public:
MyClass(int value) : _value(value), type(0) {}
int getValue() const { return _value; }
int type;
private:
int _value;
};
// 使用pybind11将MyClass绑定到Python
PYBIND11_MODULE(demo, m) {
pybind11::class_<MyClass>(m, "MyClass")
.def(pybind11::init<int>()) // 绑定构造函数
.def_readwrite("type", &MyClass::type); // 绑定成员变量
.def("getValue", &MyClass::getValue); // 绑定成员函数
}
python代码
obj = demo.MyClass(42)
print(obj.type)
print(obj.getValue())