概述

类似 Boost.Python, 不过去掉了对 Boost 库的依赖,更轻便,便于绑定到 python

模块创建

  • 例子

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    #include <pybind11/pybind11.h>
    
    int add(int i, int j) {
        return i + j;
    }
    
    PYBIND11_MODULE(example, m) {
        m.doc() = "pybind11 example plugin"; // optional module docstring
    
        m.def("add", &add, "A function which adds two numbers");
    }
  • 重点

    • 使用 PYBIND11_MODULE 宏函数

      • 宏函数接口

        • 模块名指定

          • 注意,不是字符串
      • 宏函数 body

        • 说明模块本身信息

相关对象

pybind11 命名空间

1
2
3
#include <pybind11/pybind11.h>

namespace py = pybind11;
  • 使用惯例

模块类

  • py::module_

    • PYBIND11_MODULE 宏函数的内部形参 m
  • 方法

    • m.def()

      • py::module_::def()
      • 绑定函数
    • m.doc() = str

      • module_::doc() = "pybind11 example module doctring"
      • 设置模块文档
    • m.attr("variable_name") = …

      • 定义模块级变量

py::object

  • 即 Python object 对应类型

py::list

  • Python list 类型

类型转换

相关函数

  • py::cast

    • 强制类型转换
    • eg:

      1
      
        py::object world = py::cast("World")
      • python 等价: m.world = "World"

_module::def 函数绑定

  • 相关语句

    1
    
    using namespace pybind11::literals;
    • "_a"

位置参数 –> 传递参数名称

  • 用 py::arg 或 _a

    1
    2
    3
    4
    5
    
    // regular notation
    m.def("add1", &add, py::arg("i"), py::arg("j"));
    // shorthand
    using namespace pybind11::literals;
    m.def("add2", &add, "i"_a, "j"_a);
    • python 等价: add(i, j)

      • 让 python 探知到 第一、第二个参数的名称就是 "i"、"j"

默认参数

  • py::arg("i") = default_param
  • eg:

    1
    2
    3
    4
    5
    
    // regular notation
    m.def("add1", &add, py::arg("i") = 1, py::arg("j") = 2);
    
    // shorthand
    m.def("add2", &add, "i"_a=1, "j"_a=2);

函数文档

  • 在 m.def(…., "docstring"),最后一个参数指定 docstring 文档
  • eg:

    1
    
    class_.def("set", &Pet::set, "Set the pet's age")

lambda 函数绑定

1
m.def("pet_store2", []() { return std::unique_ptr<PolymorphicPet>(new PolymorphicDog); });

绑定策略 return_value_policy

  • 作用

    • 规定函数返回值,所有权处置问题

      • python 所有还是 C++ 所有,意味着 引用计数的处理方式
      • 数据使用完成后的清理问题
  • 特殊场景

    • Python 清理了 C++ 静态区对象

      • eg:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        
        /* Function declaration */
        // _data 指向 C++ 静态存储区
        Data *get_data() { return _data; /* (pointer to a static data structure) */ }
        ...
        
        /* Binding code */
        // return_value_policy::automatic --> 对于指针pointer 变成 ::take_ownership, 
        // python 会 调用 delete() ===> 导致错误析构 ==> 程序崩溃
        m.def("get_data", &get_data); // <-- KABOOM, will cause crash when called from Python
        
        • 正确做法: m.def("get_data", &get_data, py::return_value_policy::reference);

return_value_policy 枚举值

  • return_value_policy::take_ownership

    • Python 负责
    • 适用对象

      • heap 区 指针 pointer
    • 安全性

      • 危险

        • 多次析构
        • 未初始化
  • return_value_policy::copy

    • Python 负责
    • 适用对象

      • lvalue

        • 引用等
    • 安全性

      • 安全

        • python 端获取的是全新拷贝
  • return_value_policy::move

    • Python 负责
    • 适用范围

      • rvalue

        • 右值
    • 安全性

      • 安全

        • 全新拷贝(python 端)
  • return_value_policy::reference

    • C++ 负责
    • 安全性

      • 危险

        • C++ 端已经析构,Python 还在使用
  • return_value_policy::reference_internal

    • 类似 reference policy

      • 但是有 keep_alive<0, 1> call policy 增强
      • 引用还依赖 this 或 self 对象的存在

        • 即:返回值 delete 前,this 或 self 绝对不允许被 先 delete 回收
    • 安全性

      • 比 reference policy 高
    • def_property 和 def_readwrite 等的默认 policy
  • return_value_policy::automatic

    • py::class_::def() 默认 policy
    • 处理方式

      • pointer -> take_ownership

        • 潜在危险: pointer 可能指向不可被析构的对象,如静态区对象
      • lvalue, 引用 -> copy
      • rvale -> move
  • return_value_policy::automatic_reference

    • 非面向用户 policy

def_property, def_readwrite 等默认 policy

  • return_value_policy::reference_internal

py::class_::def() 默认 policy

  • return_value_policy::automatic

注意事项

  • 指定 policy 只适用于,pybind11 第一次见到这个对象

    • 通过 类型 和 内存地址判断
  • return_value_policy::reference 用法要参考 call policies
  • 可以通过 smart pointer 自动完成 引用计数问题

    • 参考:

    • 返回 smart pointers 时,可以不用指定 return_value_policy

调用策略 call policy

  • 和 return_value_policy 一起使用
  • 作用

    • 指定函数调用时参数间的依赖关系
  • 代码例子

keep_alive()

  • 接口

    • keep_alive<Nurse, Patient>()

      • Nurse 销毁后,Patient 才可以被销毁
      • 即:存活时间 Nurse <= Patient
  • 参数

    • 说明

      • 整型
      • 参数 >= 1
      • 0 用于表示返回值
    • 1

      • this 指针
    • >= 2

      • 函数参数表
  • 使用案例

    • list.append

      • eg:

        1
        2
        
          py::class_<List>(m, "List")
              .def("append", &List::append, py::keep_alive<1, 2>());
      • 1 -> this -> 容器 list
      • 2 -> item -> 子部件
      • 寿命: 容器 list <= item

        • 即容器不在了,但是 item 依然存在,容器只是对 item 引用了一次

call_guard<T>

  • 参考

  • 一种<类 Deamon 工具>,快捷调用工具
  • 等价代码

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 实际使用方式
    m.def("foo", foo, py::call_guard<T>());
    
    // 等价代码
    m.def("foo", [](args...) {
        T scope_guard;
        return foo(args...); // forwarded arguments
    });
  • 使用案例

    • 与 py::gil_scoped_release 一起使用

      1
      2
      3
      4
      5
      6
      7
      8
      
      m.def("call_go", [](Animal *animal) -> std::string {
        /* Release GIL before calling into (potentially long-running) C++ code */
        py::gil_scoped_release release;
        return call_go(animal);
      });
      
      // call_guard 实现方式
      m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());

_module::attr() 属性处理

  • eg:

    1
    2
    3
    4
    5
    
    PYBIND11_MODULE(example, m) {
        m.attr("the_answer") = 42;
        py::object world = py::cast("World");
        m.attr("what") = world;
    }

面向对象 —- 类的处理

  • eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    #include <pybind11/pybind11.h>
    
    namespace py = pybind11;
    
    PYBIND11_MODULE(example, m) {
        py::class_<Pet>(m, "Pet")
            .def(py::init<const std::string &>())
            .def("setName", &Pet::setName)
            .def("getName", &Pet::getName);
    }

类的声明

  • py::class_<CppClass, BaseClass>(m, "ClassNameInPython")

方法绑定 def()

  • py::class_::def()

简单方法

  • py::class_.def("methodNameInPython", &CppClass::methodName)
  • 不涉及重载等

重载

  • 实现方式

    1. 多条语句,对应多个重载方法
    2. 函数的类型转换,避免重名问题

      • C++ 11

        • static_cast<函数指针类型>(&funcName)
        • class_.def("overloadedMethod", static_cast<function_pointer_type>(&methodName))
      • C++ >= 14

        • py::overload_cast<参数类型>(&funcName)
        • class_.def("overloadedMethod", py::overload_cast<func_param_type_list>(&methodName))
        • C++ 11 使用 overload_cast 方法

          1
          2
          
          template <typename... Args>
          using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;
  • eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    // ------------------ 重载函数定义
    struct Pet {
        Pet(const std::string &name, int age) : name(name), age(age) { }
    
        void set(int age_) { age = age_; }
        void set(const std::string &name_) { name = name_; }
    
        std::string name;
        int age;
    };
    
    
    // ------------------ 绑定重载函数
    // C++11
    py::class_<Pet>(m, "Pet")
       .def(py::init<const std::string &, int>())
       .def("set", static_cast<void (Pet::*)(int)>(&Pet::set), "Set the pet's age")
       .def("set", static_cast<void (Pet::*)(const std::string &)>(&Pet::set), "Set the pet's name");
    
    // C++ >= 14
    py::class_<Pet>(m, "Pet")
        .def("set", py::overload_cast<int>(&Pet::set), "Set the pet's age")
        .def("set", py::overload_cast<const std::string &>(&Pet::set), "Set the pet's name");
构造函数重载
  • 使用 class_.def(py::init<input_type_list>())

    1. 使用 py::init<param_type_list>()
    2. 类似普通方法的重载即可
  • eg:

    1
    2
    3
    4
    5
    
    py::class_<Pet> pet(m, "Pet");
    
    pet.def(py::init<>())
    .def(py::init<const std::string &>())
    .def_readwrite("name", &Pet::name);

lambda 函数

  • eg:

    1
    2
    3
    4
    
    pet.def("__repr__",
            [](const Pet &a) {
              return "<example.Pet named '" + a.name + "'>";
            }
  • 接口规定

    • 接口设定有 self 即可
    • 注意

      • capture 为 []

        • 不接受外部任何内容

字段绑定

  • 公有字段

    • .def_readwrite_*()
  • 私有字段

    • .def_property_*()
  • 静态字段字段

    • 公有:

      • .def_readwrite_static()
    • 私有

      • .def_property_*_static()

公有实例字段 public: int field;

  • 即:实例的 public 成员变量
  • .def_readwrite("name", &Cppclass::field)

私有实例字段 private: int field;

  • 绑定方法

    • 绑定对应的 setter, getter
  • rw: 可读可写字段

    • .def_property("name", &Cppclass::getter, &Cppclass::setter)
  • r: 只读字段

    • .def_property_readonly("name", &Cppclass::getter)
  • w: 只写字段

    • .def_property("name", nullptr, &Cppclass::setter)

公有静态字段 public: static int field

  • .def_readwrite_static()

私有静态字段 private: static int field;

  • rw:

    • .def_property_static()
  • r:

    • .def_property_readonly_static()
  • w:

    • .def_property_static("name", nullptr, &Cppclass::setter)

例子 —- static private 私有静态字段

  • 说明

    • python 角度

      • python 要求 类字段,没有 self, 但是要有一个 cls
      • 参考:static-properties
      • 实现方式

        • 修改 cpp 文件,单做一个接口第一个参数是 (py::object cls) 的 getter, setter
        • eg: public: static getCount(py::object cls) {return count;}
    • c++ 角度

      • 静态字段 初始化

        • static const 类型: 在类内部直接初始化

          • eg:

            1
            2
            3
            4
            5
            
            class Demo{
            private:
              // 立刻初始化
              static const int id = 1000;
            }
        • static non-const 类型: 在类外部(通常是 cpp 文件中)初始化

          • eg:

            1
            2
            3
            4
            5
            6
            7
            
            class Demo {
            private:
              static int count;  // 相当于 extern, 可以放到头文件中,多处出现
            };
            
            // 外部初始化, cpp 文件中,唯一一处,不会重复初始化
            int Demo::count = 0;
      • 静态字段 读取,设置

        • static getter(), setter() 添加 py::object 参数
  • 实战

    • cpp 文件

       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
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      
      #include<pybind11/pybind11.h>
      
      namespace py = pybind11;
      
      
      class Demo{
      private:
        static int score;
      
      public:
        // 用于 c++ 内部使用
        static int getScore() {return score;}
        static void setScore(int _score) {score = _score;}
      
        // 用于 python 绑定
        static int getScore(py::object cls) {return score;}
        static void setScore(py::object cls, int new_score) {score = new_score;}
      
      };
      
      int Demo::score = 100;
      
      
      PYBIND11_MODULE(demo, m){
        py::class_<Demo>(m, "Demo")
          .def_property_readonly_static("ro_score", py::overload_cast<py::object>(&Demo::getScore))
          .def_property_static("score", py::overload_cast<py::object>(&Demo::getScore), py::overload_cast<py::object, int>(&Demo::setScore));
      
      }
      
      /*
       ,* 
      # 测试结果
      
      In [3]: import demo
      
      In [4]: help(demo)
      
      
      In [5]: demo.Demo.score
      Out[5]: 100
      
      In [6]: demo.Demo.score = 100
      
      In [7]: demo.Demo.score = 200
      
      In [8]: demo.Demo.score
      Out[8]: 200
      
      In [9]: demo.Demo.ro_score
      Out[9]: 200
      
      In [10]: demo.Demo.ro_score = 300
      ---------------------------------------------------------------------------
      AttributeError                            Traceback (most recent call last)
      <ipython-input-10-94247630f43e> in <module>
      ----> 1 demo.Demo.ro_score = 300
      
      AttributeError: can't set attribute
      
      ,*/
    • CMakeLists.txt

      1
      2
      3
      4
      5
      6
      7
      8
      
      cmake_minimum_required(VERSION 3.15)
      
      project(Demo_Pybind)
      set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
      
      find_package(pybind11 REQUIRED)
      
      pybind11_add_module(demo demo.cpp)

python 端 动态属性 —- 允许 Python 内部绑定属性

  • 功能说明

    • pybind11 默认不支持 python 动态绑定属性到 C++ 对象
    • eg:

      • Dog() 类,在 C++ pybind11 绑定时,只有 name 没有 age
      • python code: dog.age = 11

        • 非法, raise AttributeError
  • hack 实现方法

    • 声明绑定 C++ 类时,传入 py::dynamic_attr()
    • eg:

      1
      2
      3
      
      py::class_<Pet>(m, "Pet", py::dynamic_attr())
          .def(py::init<>())
          .def_readwrite("name", &Pet::name);

继承

  • 实现方法

    1. 先声明所有基类
    2. 再声明子类

      • 两种语法

        • py::class_<ChildClass, ParentClass>(m, "name")

          • 使用基类的 C++版本 –> ParentClass
        • py::class_<ChildClass>(m, "name", ParentClassInstance)

          • 使用基类绑定版本,py::class_ –> ParentClassInstance

复写 —- 多态绑定 —- virtual 函数

类指针的多态

  • C++ 要求基类至少有一个 virtual 虚函数 才能拥有多态特性 pylymorphic

    • 即 Pet* pet = new Dog()
    • python 中,这里 pet 多态实现时, pet 内部存储的时 Dog 类型
  • 没有虚函数方法的实现

    • 使用 虚析构函数实现
    • eg:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      struct PolymorphicPet {
          // 虚函数--析构函数
          virtual ~PolymorphicPet() = default;
      };
      
      struct PolymorphicDog : PolymorphicPet {
          std::string bark() const { return "woof!"; }
      };
      
      // Same binding code
      py::class_<PolymorphicPet>(m, "PolymorphicPet");
      py::class_<PolymorphicDog, PolymorphicPet>(m, "PolymorphicDog")
          .def(py::init<>())
          .def("bark", &PolymorphicDog::bark);
      
      // Again, return a base pointer to a derived instance
      m.def("pet_store2", []() { return std::unique_ptr<PolymorphicPet>(new PolymorphicDog); });

override 函数重写

  • 参考

  • 问题说明

    • C++ 的 对 virtual 函数 override, python 中不能正常绑定

      • 基类因为是虚基类,所有不能创建实例,在 Python 中不能实现
  • 实现方法

    1. 添加基类的辅助类

      • 对所有的基类 virtual 函数添加宏函数修饰
      • eg:

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        
          /* 制作帮助函数 */
          class PyAnimal : public Animal {
          public:
              /* Inherit the constructors */
              /* 继承构造函数*/
              using Animal::Animal;
        
              /* Trampoline (need one for each virtual function) */
              /* 宏函数修饰 */
              std::string go(int n_times) override {
                  PYBIND11_OVERRIDE_PURE(
                      std::string, /* Return type */
                      Animal,      /* Parent class */
                      go,          /* Name of function in C++ (must match Python name) */
                      n_times      /* Argument(s) */
                  );
              }
          };
    2. 声明绑定基类时,传入帮助类

      • eg:

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        
          PYBIND11_MODULE(example, m) {
              //    在此处传入辅助类  - ↓ -, 即可
              py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal")
                  .def(py::init<>())
                  .def("go", &Animal::go);  // 注意绑定时,传入的还是真正的基类,不是辅助类
        
              py::class_<Dog, Animal>(m, "Dog")
                  .def(py::init<>());
        
              m.def("call_go", &call_go);
          }
  • virtual 函数 复写辅助宏函数

    • PYBIND11_OVERRIDE_PURE

      • 纯虚函数

        • virtual … method(…) = 0;
    • PYBIND11_OVERRIDE(return_type, BaseClass, functionName, …param_type_list)

      • 普通虚函数
    • PYBIND11_OVERRIDE_PURE_NAME

      • 纯虚函数,允许修改对应 python 中的函数名
    • PYBIND11_OVERRIDE_NAME(return_type, BaseClass, nameInPython,functionName, …param_type_list)

      • 普通虚函数,允许修改对应 python 中的函数名
  • PYBIND11_OVERRIDE_* 使用注意事项

    • 参考:general-notes-regarding-convenience-macros
    • preprocessor 导致的问题

      • 如果类型中包含 逗号“,”,预处理器解析宏发生错误
      • eg: std::array<int, 3>
    • 解决方法

      • 方法一:使用类型别名 using alias = your_type;

        • eg:

          1
          2
          3
          4
          
            // Version 1: using a type alias
            using ReturnType = MyReturnType<T1, T2>;
            using ClassType = Class<T3, T4>;
            PYBIND11_OVERRIDE(ReturnType, ClassType, func);
      • 方法二:使用 py::PYBIND11_TYPE(your_type) 包裹类型

        • eg:

          1
          2
          3
          
            // Version 2: using the PYBIND11_TYPE macro:
            PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType<T1, T2>),
                              PYBIND11_TYPE(Class<T3, T4>), func)

python 类 继承 C++ 类

  • __init__ 函数处理

    1. 必须调用 ParentClass.__init__()

      • 避免 TypeError 报错
    2. 不能使用 super(self).__init__()

      • 避免 python MRO 和 C++ 机制不同问题

内部类型

  • 绑定方式

    • 当成普通的外部类型声明即可
    • 注意

      • py::class_<OuterClass::InnerClass>(outer_class_binder, "InPythonName")
      • 注意这里的 parent 对象时 OuterClass
  • 参考

  • eg:

    1. 嵌套类

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      
      struct Pet {
          enum Kind {
              Dog = 0,
              Cat
          };
      
          struct Attributes {
              float age = 0;
          };
      
          Pet(const std::string &name, Kind type) : name(name), type(type) { }
      
          std::string name;
          Kind type;
          Attributes attr;
      };
    2. 绑定

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      py::class_<Pet> pet(m, "Pet");
      
      pet.def(py::init<const std::string &, Pet::Kind>())
          .def_readwrite("name", &Pet::name)
          .def_readwrite("type", &Pet::type)
          .def_readwrite("attr", &Pet::attr);
      
      py::enum_<Pet::Kind>(pet, "Kind")
          .value("Dog", Pet::Kind::Dog)
          .value("Cat", Pet::Kind::Cat)
          .export_values();
      
      py::class_<Pet::Attributes> attributes(pet, "Attributes")
          .def(py::init<>())
          .def_readwrite("age", &Pet::Attributes::age);

Enum 类型

声明 py::enum_

  • 使用 py::enum_<CppEnum>(parent, "inPythonName")

    • parent

      • 模块 m 或者 外部类 OuterClassBinder

绑定枚举值

  • .value("nameInPython", &EnumItem)

hack 支持位运算

  • 支持运算符

    • or
    • xor
    • and
    • supports rudimentary arithmetic and bit-level operations like comparisons, and, or, xor, negation, etc
  • 声明时,传入 py::arithmetic()
  • eg:

    1
    
    py::enum_<Pet::Kind>(pet, "Kind", py::arithmetic()) pet_kind;

例子

1
2
3
4
py::enum_<Pet::Kind>(pet, "Kind")
    .value("Dog", Pet::Kind::Dog)
    .value("Cat", Pet::Kind::Cat)
    .export_values();