文章目录

  • 1.说明
  • 2.下载
  • 3.安装
  • 4.检验
  • 5.绑定函数
  • 5.1. c++代码
  • 5.2. 编译
  • 5.3. python中引入
  • 5.4. pybind11::arg定义keyword
  • 5.5. 默认参数
  • 6绑定class
  • 6.1. c++代码
  • 6.2. 编译
  • 6.3. python中引入
  • 6.4. python风格的property
  • 6.5. 继承风格的python绑定
  • 7.c++与python的类型转换
  • 7.1 python中访问c++定义的类型【无内存拷贝】
  • 7.2 c++中访问python定义的类型【无内存拷贝】
  • 7.3 在c++和python的原生类型之间做转换【内存拷贝】
  • 7.4 如何实现pybind11中未实现的类型转换,如cv::mat(c++)转numpy(python)
  • 8.注册回调函数


1.说明

在python中引用c++代码的几种常见方法:

  1. python的C-API(Python.h)
  2. SWIG
  3. Python的ctypes模块
  4. Cython
  5. Boost::Python
  6. 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中引入

python 接口封装api python封装c++接口_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)