天天看點

Pybind11基礎

Pybind11基礎

文章目錄

      • Pybind11基礎
        • 參考
        • 安裝
        • 函數綁定
          • 基本Demo
          • 傳回值政策
          • 以Python對象作為參數
            • Dict
            • List
          • 參數關鍵字
          • 預設參數
          • Non-converting參數
          • 禁止/允許 空參數
          • template的使用
        • 類/結構體/枚舉
          • 類/結構體Demo
          • 類的繼承
          • 重載
          • 重載虛函數
          • 枚舉和内部類型
        • 導出變量
        • 智能指針

參考

  • pybind11官方文檔
  • 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

    傳回值的生命周期與父對象的生命周期相綁定,即被調用函數或屬性的

    this

    self

    對象。這種政策與reference政策類似,但附加了

    keep_alive<0, 1>

    調用政策保證傳回值還被Python引用時,其父對象就不會被垃圾回收掉。這是由

    def_property

    def_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_OVERRIDE

    PYBIND11_OVERRIDE_PURE_NAME

    PYBIND11_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> > obj(m, "Example");

    • 需要注意的是當有AB類被綁定為std::shared_ptr,同時B為A的屬性類,則B在A中的定義也應當為std::shared_ptr,或者B類本身在定義的時候需要制定可以共享