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
使用
來移動傳回值的内容到新執行個體,新執行個體的所有權在Python。該政策相對來說比較安全,因為兩個執行個體的生命周期是分離的。std::move
return_value_policy::reference
引用現有對象,但不擁有所有權。C++側負責該對象的生命周期管理,并在對象不再被使用時負責析構它。注意:當Python側還在使用引用的對象時,C++側删除對象将導緻未定義行為。 return_value_policy::reference_internal
傳回值的生命周期與父對象的生命周期相綁定,即被調用函數或屬性的
或this
對象。這種政策與reference政策類似,但附加了self
調用政策保證傳回值還被Python引用時,其父對象就不會被垃圾回收掉。這是由keep_alive<0, 1>
、def_property
建立的屬性getter方法的預設傳回值政策。def_readwrite
return_value_policy::automatic
當傳回值是指針時,該政策使用
。反之對左值和右值引用使用return_value_policy::take_ownership
。請參閱上面的描述,了解所有這些不同的政策的作用。這是return_value_policy::copy
封裝類型的預設政策。py::class_
return_value_policy::automatic_reference
和上面一樣,但是當傳回值是指針時,使用
政策。這是在C++代碼手動調用Python函數和使用return_value_policy::reference
中的casters時的預設轉換政策。你可能不需要顯式地使用該政策。pybind11/stl.h
- 使用方法,類似這樣:
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
外,不會從pybind11命名空間引入其他不必要的東西。literals
// 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++基類作為派生類
的模闆參數;2)将基類名作為class_
的參數綁定到派生類。兩種方法是等效的。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
宏的功能類似,主要用于C函數名和Python函數名不一緻的時候。以PYBIND11_OVERRIDE_NAME
為例:__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);
導出變量
- 使用
函數來注冊需要導出到Python子產品中的C++變量。attr
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可以正常傳回智能指針,但是無法以智能指針作為參數傳遞
- 文檔中無法作為參數傳遞的是
,給出的解釋是:如果用智能指針作為參數在傳遞的時候就意味着Python放棄所有權,而由于該對象可能在Python中别的地方被引用,這通常是不行的。std::unique_ptr
- 目前的測試中發現
也無法當做參數傳遞,當然也可能是個人沒有測試到位。std::shared_ptr
- 文檔中無法作為參數傳遞的是
- 我們在做類别綁定的時候Pybind,名為type的類型的預設值為
也就是說,,這意味着當Python的引用計數變為0時将釋放該對象。std::unique_ptr< type >
- 當然這是可以指定的必入換成
: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類本身在定義的時候需要制定可以共享
- 當然這是可以指定的必入換成