当前位置: 首页 > 工具软件 > pybind11 > 使用案例 >

Pybind11基础

汝楷
2023-12-01

Pybind11基础

参考

安装

  • 其他方法安装可以参考:https://pybind11.readthedocs.io/en/stable/installing.html

  • 本次采用了Bazel作为管理器,因此在安装的时候只需要在third_party中构建对应的bzl的rule即可

  • """Loads the pybind11 library"""
    
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    def repo():
        http_archive(
            name = "pybind11_bazel",
            sha256 = "8f546c03bdd55d0e88cb491ddfbabe5aeb087f87de2fbf441391d70483affe39",
            strip_prefix = "pybind11_bazel-26973c0ff320cb4b39e45bc3e4297b82bc3a6c09",
            urls = [
                "https://qcraft-web.oss-cn-beijing.aliyuncs.com/cache/packages/pybind11_bazel-26973c0ff320cb4b39e45bc3e4297b82bc3a6c09.tar.gz",
                "https://github.com/pybind/pybind11_bazel/archive/26973c0ff320cb4b39e45bc3e4297b82bc3a6c09.tar.gz",
            ],
        )
    
        http_archive(
            name = "pybind11",
            build_file = "@pybind11_bazel//:pybind11.BUILD",
            sha256 = "6cd73b3d0bf3daf415b5f9b87ca8817cc2e2b64c275d65f9500250f9fee1677e",
            strip_prefix = "pybind11-2.7.0",
            urls = [
                "https://qcraft-web.oss-cn-beijing.aliyuncs.com/cache/packages/pybind11-2.7.0.tar.gz",
                "https://github.com/pybind/pybind11/archive/refs/tags/v2.7.0.tar.gz",
            ],
        )
    
    

函数绑定

基本Demo
#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");
}
返回值策略
  • 参考

  • 返回值策略描述
    return_value_policy::take_ownership引用现有对象(不创建一个新对象),并获取所有权。在引用计数为0时,Pyhton将调用析构函数和delete操作销毁对象。
    return_value_policy::copy拷贝返回值,这样Python将拥有拷贝的对象。该策略相对来说比较安全,因为两个实例的生命周期是分离的。
    return_value_policy::move使用std::move来移动返回值的内容到新实例,新实例的所有权在Python。该策略相对来说比较安全,因为两个实例的生命周期是分离的。
    return_value_policy::reference引用现有对象,但不拥有所有权。C++侧负责该对象的生命周期管理,并在对象不再被使用时负责析构它。注意:当Python侧还在使用引用的对象时,C++侧删除对象将导致未定义行为。
    return_value_policy::reference_internal返回值的生命周期与父对象的生命周期相绑定,即被调用函数或属性的thisself对象。这种策略与reference策略类似,但附加了keep_alive<0, 1>调用策略保证返回值还被Python引用时,其父对象就不会被垃圾回收掉。这是由def_propertydef_readwrite创建的属性getter方法的默认返回值策略。
    return_value_policy::automatic当返回值是指针时,该策略使用return_value_policy::take_ownership。反之对左值和右值引用使用return_value_policy::copy。请参阅上面的描述,了解所有这些不同的策略的作用。这是py::class_封装类型的默认策略。
    return_value_policy::automatic_reference和上面一样,但是当返回值是指针时,使用return_value_policy::reference策略。这是在C++代码手动调用Python函数和使用pybind11/stl.h中的casters时的默认转换策略。你可能不需要显式地使用该策略。
  • 使用方法,类似这样:m.def("get_data", &get_data, py::return_value_policy::reference);

以Python对象作为参数
Dict
void print_dict(const py::dict& dict) {
    /* Easily interact with Python types */
    for (auto item : dict)
        std::cout << "key=" << std::string(py::str(item.first)) << ", "
                  << "value=" << std::string(py::str(item.second)) << std::endl;
}

// it can be exported as follow:
m.def("print_dict", &print_dict);
  • 调用
>>> print_dict({"foo": 123, "bar": "hello"})
key=foo, value=123
key=bar, value=hello
List
// 有个类型转换的过程
void print_list(const std::vector<int>& input) {
  /* Easily interact with Python types */
  for (auto in : input) std::cout << in << std::endl;
}
void print_list_string(const std::vector<std::string>& input) {
  /* Easily interact with Python types */
  for (auto in : input) std::cout << in << std::endl;
}
// 这里是吧原生的python类型包在了py::object中
void print_listv2(py::list my_list) {
    for (auto item : my_list)
        std::cout << item << " ";
}
m.def("print_list", &print_list);
m.def("print_list_string", &print_list_string); 
m.def("print_listv2", &print_listv2);
参数关键字
  • 可以使用py::arg("key_word_name")来绑定参数的关键字
m.def("add", &add, "A function which adds two numbers",
      py::arg("i"), py::arg("j"));
      
import example
example.add(i=1, j=2)  #3L
  • 更简短的方式:后缀_a会生成一个等价于arg方法的字面量
  • 使用这个后缀时,需要调用using namespace pybind11::literals来声明后缀所在的命名空间。这样除了literals外,不会从pybind11命名空间引入其他不必要的东西。
// 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);
默认参数
  • 需要借助arg才能指定默认参数
py::class_<MyClass>("MyClass").def("myFunction", py::arg("arg") = SomeType(123));
  • 同样的:_a后缀也可以这么干
// regular notation
py::class_<MyClass>("MyClass").def("myFunction", py::arg("arg") = SomeType(123));
// shorthand
py::class_<MyClass>("MyClass").def("myFunction", "arg"_a = SomeType(123));
Non-converting参数
  • 禁止参数的类型转换
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());
m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f"));
禁止/允许 空参数
  • pybind11允许将Python的None传递给函数,等同于C++中传递nullptr给函数,使用py::arg对象的.none方法来显式地使能或禁止该行为
py::class_<Dog>(m, "Dog").def(py::init<>());
py::class_<Cat>(m, "Cat").def(py::init<>());
m.def("bark", [](Dog *dog) -> std::string {
    if (dog) return "woof!"; /* Called with a Dog instance */
    else return "(no dog)"; /* Called with None, dog == nullptr */
}, py::arg("dog").none(true));
m.def("meow", [](Cat *cat) -> std::string {
    // Can't be called with None argument
    return "meow";
}, py::arg("cat").none(false));



# python
>>> from animals import Dog, Cat, bark, meow
>>> bark(Dog())
'woof!'
>>> meow(Cat())
'meow'
>>> bark(None)
'(no dog)'
>>> meow(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: meow(): incompatible function arguments. The following argument types are supported:
    1. (cat: animals.Cat) -> str

Invoked with: None
template的使用
  • 需要注意的是Python中传入的类别类型并不是所有都行,而是需要def中指定的才行
template <typename T>
T add(T& a, T& b)  //这就是一个模板函数
{
  return a + b;
}
m.def("add", &add<int>, "A function which adds two numbers");
m.def("add", &add<double>, "A function which adds two numbers");
m.def("add", &add<float>, "A function which adds two numbers");

类/结构体/枚举

类/结构体Demo
  • 类与结构体的绑定方式基本相同
#include <pybind11/pybind11.h>
namespace py = pybind11;


struct Pet {
    Pet(const std::string &name) : name(name) { }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }

    std::string name;
};



PYBIND11_MODULE(example, m) {
    py::class_<Pet>(m, "Pet")
        .def(py::init<const std::string &>())
        .def("setName", &Pet::setName)
        .def("getName", &Pet::getName)
      	.def_readwrite("name", &Pet::name);
  			// 当然上面的可以用def_property替代
  			// .def_property("name", &Pet::getName, &Pet::setName)
}

# python
>>> import example
>>> p = example.Pet("Molly")
>>> print(p)
<example.Pet object at 0x10cd98060>
>>> p.getName()
u'Molly'
>>> p.setName("Charly")
>>> p.getName()
u'Charly'
类的继承
  • pybind11提供了两种方法来指明继承关系:1)将C++基类作为派生类class_的模板参数;2)将基类名作为class_的参数绑定到派生类。两种方法是等效的。
struct Pet {
    Pet(const std::string &name) : name(name) { }
    std::string name;
};

struct Dog : Pet {
    Dog(const std::string &name) : Pet(name) { }
    std::string bark() const { return "woof!"; }
};


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

// Method 1: template parameter:
py::class_<Dog, Pet /* <- specify C++ parent type */>(m, "Dog")
    .def(py::init<const std::string &>())
    .def("bark", &Dog::bark);

// Method 2: pass parent class_ object:
py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */)
    .def(py::init<const std::string &>())
    .def("bark", &Dog::bark);
重载
  • 重载方法即拥有相同的函数名,但入参不一样的函数:
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;
};
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");

// c++ 11 中使用py::detail::overload_cast_impl替代
template <typename... Args>
using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;

py::class_<Pet>(m, "Pet")
    .def("set", overload_cast_<int>()(&Pet::set), "Set the pet's age")
    .def("set", overload_cast_<const std::string &>()(&Pet::set), "Set the pet's name");
  • 如果是基于const的重载,需要使用py::const标识。
struct Widget {
    int foo(int x, float y);
    int foo(int x, float y) const;
};

py::class_<Widget>(m, "Widget")
   .def("foo_mutable", py::overload_cast<int, float>(&Widget::foo))
   .def("foo_const",   py::overload_cast<int, float>(&Widget::foo, py::const_));
重载虚函数
  • 假设有一个含有虚函数的C++类或接口,我们想在Python中重载虚函数。
class Animal {
public:
    virtual ~Animal() { }
    virtual std::string go(int n_times) = 0;
};

class Dog : public Animal {
public:
    std::string go(int n_times) override {
        std::string result;
        for (int i=0; i<n_times; ++i)
            result += "woof! ";
        return result;
    }
};
  • 定义一个辅助类作为跳板
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) */
        );
    }
};

// Animal类的绑定代码也需要一些微调:
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);
}
// pybind11通过向`class_`指定额外的模板参数PyAnimal,让我们可以在Python中继承Animal类。
  • 定义纯虚函数时需要使用PYBIND11_OVERRIDE_PURE宏,而有默认实现的虚函数则使用PYBIND11_OVERRIDEPYBIND11_OVERRIDE_PURE_NAMEPYBIND11_OVERRIDE_NAME 宏的功能类似,主要用于C函数名和Python函数名不一致的时候。以__str__为例:
std::string toString() override {
  PYBIND11_OVERRIDE_NAME(
      std::string, // Return type (ret_type)
      Animal,      // Parent class (cname)
      "__str__",   // Name of method in Python (name)
      toString,    // Name of function in C++ (fn)
  );
}
枚举和内部类型
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;
};

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);

导出变量

  • 使用attr函数来注册需要导出到Python模块中的C++变量。
PYBIND11_MODULE(example, m) {
    m.attr("the_answer") = 42;
    py::object world = py::cast("World");
    m.attr("what") = world;
}
``

Python中使用如下:
```pyhton
>>> import example
>>> example.the_answer
42
>>> example.what
'World'

智能指针

  • 参考依据:官方文档
  • 从官方文档的描述来看,Pybind11可以正常返回智能指针,但是无法以智能指针作为参数传递
    • 文档中无法作为参数传递的是std::unique_ptr,给出的解释是:如果用智能指针作为参数在传递的时候就意味着Python放弃所有权,而由于该对象可能在Python中别的地方被引用,这通常是不行的。
    • 目前的测试中发现std::shared_ptr也无法当做参数传递,当然也可能是个人没有测试到位。
  • 我们在做类别绑定的时候Pybind,名为type的类型的默认值为std::unique_ptr< type >也就是说,,这意味着当Python的引用计数变为0时将释放该对象。
    • 当然这是可以指定的必入换成std::shared_ptr
    • py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example");
    • 需要注意的是当有AB类被绑定为std::shared_ptr,同时B为A的属性类,则B在A中的定义也应当为std::shared_ptr,或者B类本身在定义的时候需要制定可以共享
 类似资料: