pybind11-c++封装python可用的包
pybind11实现python调用c++,或者反之
1.说明
在python中引用c++代码的几种常见方法:
- python的C-API(Python.h)
- SWIG
- Python的ctypes模块
- Cython
- Boost::Python
- Pybind11
Pybind11是一个仅包含头文件的库,它可以实现在python调用c++,或者反之。它类似于Boost::Python。
2.下载
git clone https://github.com/pybind/pybind11.git
3.安装
pybind11只有头文件,还需要安装的原因是: 测试环境是否满足、是否兼容。并且会将pybind11的头文件拷贝头系统的目录下。
mkdir build
cd build
cmake ..
make check -j 4
make install
export PYTHONPATH=$PYTHONPATH:/home/**/**/pybind11/pybind11
直接把头文件全部放到自己的项目中也是可以的。
4.检验
python3 -m pybind11 --includes
此语句的意思是用于包pybind11,参数是–includes。
如果能正常执行,会输出pybind11的头文件的路径。
5.绑定函数
5.1. c++代码
//file helloworld.cp
#include <pybind11/pybind11.h>
int Add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(HelloPybind11, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("Add", &Add, "A function which adds two numbers");
}
其中,PYBIND11_MODULE的第一个参数是变成报名,也就是最后会生成的文件HelloPybind11.cpython-38-x86_64-linux-gnu.so的前缀。
5.2. 编译
g++ -O3 -Wall -shared -std=c++11 -fPIC -I/usr/include/python3.8 `python3 -m pybind11 --includes` helloworld.cpp -o HelloPybind11`python3-config --extension-suffix`
5.3. python中引入
> import HelloPybind11
> HelloPybind11.Add(1,2)
3
5.4. pybind11::arg定义keyword
c++代码中的PYBIND11_MODULE中,改为
PYBIND11_MODULE(Add, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("Add", &Add,pybind11::arg("i"),pybind11::arg("j"), "A function which adds two numbers");
}
这样就可以实现python中的Add(i=2,j=1)的调用形式
5.5. 默认参数
c++代码中的PYBIND11_MODULE中,改为
PYBIND11_MODULE(Add, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("Add", &Add,pybind11::arg("i")=1,pybind11::arg("j")=1, "A function which adds two numbers");
}
这样就可以实现python中的Add()的调用形式,默认输出1+1=2
6绑定class
有了上述Helloworld的例子,就可以实现python调用c++中的函数。接下来是通过pybind11绑定class,python可以调用c++的类。
6.1. c++代码
//file HelloPybind11Class.cpp
#include <pybind11/pybind11.h>
#include <string>
class HelloClass
{
public:
HelloClass(const std::string &name):name_(name){}
void setName(const std::string &name){name_ = name;}
const std::string &getName()const {return name_;}
private:
std::string name_;
};
PYBIND11_MODULE(HelloClass,m)
{
pybind11::class_<HelloClass>(m,"HelloClass")
.def(pybind11::init<const std::string &>())
.def("setName",&HelloClass::setName)
.def("getName",&HelloClass::getName);
}
6.2. 编译
同样的编译方法
6.3. python中引入
注意:这其实实现了一次c++的中的自定义类型转换为python的类型。因为HelloClass类实在c++中定义的,但却在python中直接调用。
6.4. python风格的property
python中class某个属性一般会直接通过 .
运算符直接获取,例如 x.name_
,pybind11也可以实现
PYBIND11_MODULE(HelloClass,m)
{
pybind11::class_<HelloClass>(m,"HelloClass",pybind11::dynamic_attr())
.def(pybind11::init<const std::string &>())
.def("setName",&HelloClass::setName)
.def("getName",&HelloClass::getName)
.def_property("property",&HelloClass::getName,&HelloClass::setName);
}
这样在python中就可以这样调用:
% python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import HelloClass
>>> x = HelloClass.HelloClass('p')
>>> x.property
'p'
>>>
>>> x.age =10//虽然c++中未定义属性age ,但是pybind11::dynamic_attr()可以实现python中的动态类型
>>> x.age
10
6.5. 继承风格的python绑定
c++中class如若存在继承关系,那么在绑定的是否把父类绑定,再把子类中所有方法绑定和父类中所有方法绑定,就可以实现继承。但是这样需要子类把父类方法重复绑定,代码量大且不都优雅。有两种方法实现继承类的绑定。
c++代码:
class A{
public:
A(const std::string &name) : name_(name) { }
void setName(const std::string &name) { name_ = name; }
const std::string &getName() const { return name_; }
private:
std::string name_;
};
class B: public A{
public:
B(const std::string& name, int age) : A(name),age_(age){}
const int getAge() const {return age_; }
void setAge(int age) {age_ = age;}
private:
int age_;
};
绑定代码:
PYBIND11_MODULE(HelloClass, m) {
pybind11::class_<A>(m, "A",pybind11::dynamic_attr())
.def(pybind11::init<const std::string &>())
.def("setName", &A::setName)
.def("getName", &A::getName)
.def_property("name_", &A::getName, &A::setName);
//最笨的绑定方法
pybind11::class_<B>(m, "B",pybind11::dynamic_attr())
.def(pybind11::init<const std::string &, int>())
.def("setName", &B::setName)
.def("getName", &B::getName)
.def("setAge", &B::setAge)
.def("getAge", &B::getAge)
.def_property("age_", &B::getAge, &B::setAge)
.def_property("name_", &B::getName, &B::setName);
// 方法1: template parameter
pybind11::class_<B, A>(m, "B")
.def(pybind11::init<const std::string &, int>())
.def("setAge", &B::setAge)
.def("getAge", &B::getAge)
.def_property("age_", &B::getAge, &B::setAge);
//方法2: pass parent class_ object:
pybind11::class_<B>(m, "B", A)
.def(pybind11::init<const std::string &, int>())
.def("setAge", &Syszux::setAge)
.def("getAge", &Syszux::getAge)
.def_property("age_", &Syszux::getAge, &Syszux::setAge);
}
7.c++与python的类型转换
此部分的解释见官方文档: 《pybind11 documnet》
7.1 python中访问c++定义的类型【无内存拷贝】
这中情况其实在上述的6.3中已经举例,HelloClass就是一个c++中所定义的类型,python通过封装的方法,可以直接调用
7.2 c++中访问python定义的类型【无内存拷贝】
python中的原生类型,需要在c++中访问,例如:
void print_list(py::list my_list) {
for (auto item : my_list)
std::cout << item << " ";
}
>>> print_list([1, 2, 3])
1 2 3
7.3 在c++和python的原生类型之间做转换【内存拷贝】
如下代码,c++的参数类型是std::vector,python在调用的时候入参类型是list,这就需要在两种类型之间做转换。也就是说pybind11实现了std::vector和python中list之间的自动类型转换。
void print_vector(const std::vector<int> &v) {
for (auto item : v)
std::cout << item << "\n";
}
>>> print_vector([1, 2, 3])
1 2 3
7.4 如何实现pybind11中未实现的类型转换,如cv::mat(c++)转numpy(python)
实现方法是使用原生的Python C API calls,但必须熟悉python的引用计数技术。
详见《pybind11-Custom type casters》
一个优秀的实现了cv::mat与numpy之间相互转的库:
https://github.com/edmBernard/pybind11_opencv_numpy/blob/master/ndarray_converter.cpp
该库实现numpy与mat相互转换的方法大体上是:
- 实例化一个pybind11::detail::type_caster的模板
- 实现了一个内存分配器NumpyAllocator
- mat转化为numpy时直接调用mat的copyTo函数
- 使用umat(以利用可能的openCL?)
- numpy转mat时,先进行数据类型的判断,然后调用mat的相应构造函数
- numcy转mat进行了needcopy和needcast的判断(没有看明白判断逻辑)
8.注册回调函数
c++代码:
void Zcam::RegisterExceptionHandler(std::function<void(int, const char* p)> excepton_handler)
{
this->excepton_handler_ = excepton_handler;
}
python代码:
def exception(a,b):
#t = threading.currentThread()
#print('exception Thread id : %d' % t.ident)
print('exception code:',a,b)
h264decoder = decoder.Zcam(camera_ip,port)
h264decoder.RegisterExceptionHandler(exception)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)