• pybind11-c++封装python可用的包


    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
    
    • 1

    3.安装

    pybind11只有头文件,还需要安装的原因是: 测试环境是否满足、是否兼容。并且会将pybind11的头文件拷贝头系统的目录下。

    mkdir build
    cd build
    cmake ..
    make check -j 4
    make install
    export PYTHONPATH=$PYTHONPATH:/home/**/**/pybind11/pybind11	 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    直接把头文件全部放到自己的项目中也是可以的。

    4.检验

    python3 -m pybind11 --includes
    
    • 1

    此语句的意思是用于包pybind11,参数是–includes。
    如果能正常执行,会输出pybind11的头文件的路径。

    5.绑定函数

    5.1. c++代码

    //file helloworld.cp
    #include 
      
    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中,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`
    
    • 1

    5.3. python中引入

    > import HelloPybind11
    > HelloPybind11.Add(1,2)
    3
    
    • 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");
    }
    
    • 1
    • 2
    • 3
    • 4

    这样就可以实现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");
    }
    
    • 1
    • 2
    • 3
    • 4

    这样就可以实现python中的Add()的调用形式,默认输出1+1=2

    6绑定class

    有了上述Helloworld的例子,就可以实现python调用c++中的函数。接下来是通过pybind11绑定class,python可以调用c++的类。

    6.1. c++代码

    //file HelloPybind11Class.cpp
    #include 
    #include 
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样在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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    绑定代码:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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 << " ";
    }
    
    • 1
    • 2
    • 3
    • 4
    >>> print_list([1, 2, 3])
    1 2 3
    
    • 1
    • 2

    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";
    }
    
    • 1
    • 2
    • 3
    • 4
    >>> print_vector([1, 2, 3])
    1 2 3
    
    • 1
    • 2

    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;
    }
    
    • 1
    • 2
    • 3
    • 4

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    正点原子开拓者FPGA,程序固化下载到板子里面
    day12-内核与文件系统衔接流程
    java计算机毕业设计高校教材征订管理系统MyBatis+系统+LW文档+源码+调试部署
    UserAgent如何使用
    MATLAB给数据加噪声/扰动
    【论文复现】QuestEval:《QuestEval: Summarization Asks for Fact-based Evaluation》
    使用pyqt5制作简单计分桌面应用
    SDUT OJ《算法分析与设计》搜索算法
    比较三种非破坏性处理数组的方法
    Google Earth Engine(GEE)——建立一个图表(ui.Chart.array.values)chart散点图
  • 原文地址:https://blog.csdn.net/sinat_36304757/article/details/126690895