0.内容概览
1.库安装
1.1 项目子模块形式
将pybind11作为项目子模块包含进项目中:
>> git submodule add -b stable git@github.com:pybind/pybind11.git [工程路径]
工程中使用时,直接在CMakeLists.txt文件中添加pybind11路径即可:
add_subdirectory([工程路径])
1.2 使用pip、conda或vcpkg包管理器安装
# 1.使用pip安装
>> pip install pybind11
# 2.全局安装
>> pip install "pybind11[global]"
# 3.使用conda安装
>> conda install pybind11
# 4.使用vcpkg安装
>> vcpkg install pybind11
工程中使用时,在CMakeLists.txt文件中添加以下内容:
set(pybind11_DIR [pybind11安装路径]/pybind11/share/cmake/pybind11)
find_package(pybind11 REQUIRED)
2.绑定函数
2.1 简单函数绑定
- (1) 创建一个
example.cc
文件,内容如下
#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 这个是可选项
m.def("add", &add, "A function that adds two numbers");
}
上述代码中,PUBIND11_MODULE(pybinder, m)
中第一个变量声明了模块名为 pybinder
用于后续python调用时 import
声明,第二个变量定义了一个类型为 py::module_
类型的变量 m
,这是创建绑定的主入口。方法 module_::def()
生成暴露 add()
函数的绑定代码给python。
pybind11
是 header-only
库,因此无需链接任何特定库,并且没有中间翻译步骤。
- (2) 简单编译方法:
>> g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cc -o example$(python3-config --extension-suffix)
- (3) 测试生成库:
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(1, 3)
4
2.2 关键字参数
- (1) 增加关键字参数
使用简单代码修改,就可以告知python关于参数的命名(示例中的"i"和"j"):
#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 这个是可选项
m.def(
"add",
&add,
"A function that adds two numbers",
py::arg("i"),
py::arg("j")
);
}
- (2) 更简洁用法
更简洁一点用法可以使用简略用法:
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 这个是可选项
m.def(
"add",
&add,
"A function that adds two numbers",
"i"_a,
"j"_a
);
}
这里 _a
后缀的形式等价于 py::arg()
,注意这个用法必须先使用命名空间 using namespace pybind11::literals;
。
- (3) 默认参数
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 这个是可选项
m.def(
"add",
&add,
"A function that adds two numbers",
"i"_a = 1,
"j"_a = 2
);
}
- (4) 导出变量
使用 attr
函数来暴露一个来自C++的值到模块中:
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // 这个是可选项
m.def(
"add",
&add,
"A function that adds two numbers",
"i"_a = 1,
"j"_a = 2
);
m.attr("__version__") = "V0.1.0";
}
- (5) 再次编译测试
>> g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cc -o example$(python3-config --extension-suffix)
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(j = 12, i = 13)
25
>>> example.add(j = 12)
13
>>> example.add(i = 12)
14
>>> example.add()
3
>>> example.__version__
'V0.1.0'
3.绑定自定义类型
3.1 绑定类
- (1)
Pet
数据结构定义
绑定一个名为 Pet
的自定义C++数据结构,它的定义如下:
// Pet.h
#pragma once
#include <string>
class Pet {
public:
explicit Pet(const std::string &name);
~Pet() = default;
void setName(const std::string &name);
const std::string &getName() const;
public:
int age_;
private:
std::string name_;
};
// Pet.cc
#include "Pet.h"
Pet::Pet(const std::string &name) : name_(name) {
}
void Pet::setName(const std::string &name) {
name_ = name;
}
const std::string &Pet::getName() const {
return name_;
}
- (2) 添加绑定
绑定代码如下:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet");
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
}
py::class_
为C++类(class
)或结构体(struct
)类型创建绑定,py::init()
函数获取构造函数参数类型作为模板参数来封装构造函数。
- (3) 编译测试
>> g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cc Pet.cc -o example$(python3-config --extension-suffix)
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet("Molly")
>>> pet.getName()
'Molly'
>>> pet.setName("Charly")
>>> pet.getName()
'Charly'
3.2 绑定 lambda
函数
可以绑定一个 __repr__
方法来打印 Pet
内容,返回对人较为友好格式,具体内容如下:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet");
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
pet.def("__repr__", [](const Pet &a) {
return "<example.Pet named '" + a.getName() + "'>";
});
}
编译过程和前面一样,测试:
python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet("Molly")
>>> pet
<example.Pet named 'Molly'>
3.3 实例和静态字段
我们可以使用 class_::def_readwrite()
方法直接暴露名字字段,类似的,可以使用 class_::def_readonly()
方法来暴露 const
字段。
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet");
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
pet.def("__repr__", [](const Pet &a) {
return "<example.Pet named '" + a.getName() + "'>";
});
pet.def_readwrite("age", &Pet::age_);
}
对于非 public
的字段,如 name_
,可以使用 class_::def_property()
或 def_property_readonly()
方法来暴露相关字段,但是这里需要显式的调用 setter
和 getter
函数:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet");
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
pet.def("__repr__", [](const Pet &a) {
return "<example.Pet named '" + a.getName() + "'>";
});
pet.def_readwrite("age", &Pet::age_);
pet.def_property("name", &Pet::getName, &Pet::setName);
}
测试:
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet("Molly")
>>> pet.name
'Molly'
>>> pet.name = 'Lilly'
>>> pet.name
'Lilly'
>>> pet.age = 20
>>> pet.age
20
3.4 动态属性
默认情况下。C++不支持这个特性,仅在使用 class_::def_readwrite()
或 class_::def_property()
显式指定时,才能支持。这里可以使用 py::dynamic_attr
标记使得 C++
类能支持动态属性:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
pet.def("__repr__", [](const Pet &a) {
return "<example.Pet named '" + a.getName() + "'>";
});
}
编译过程同上,测试如下:
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet("Molly")
>>> pet.name = "Charly"
>>> pet.name
'Charly'
>>> pet.age = 2
>>> pet.age
2
>>> pet.__dict__
{'name': 'Charly', 'age': 2}
3.5 继承和自动向下转换
唯一需要注意一点就是父类中必须至少有一个虚函数,绑定结果才能自动向下转换
// Pet.h
#pragma once
#include <string>
class Pet {
public:
explicit Pet(const std::string &name);
virtual ~Pet() = default;
void setName(const std::string &name);
const std::string &getName() const;
public:
int age_;
private:
std::string name_;
};
class Dog : public Pet {
public:
explicit Dog(const std::string &name);
std::string bark() const;
};
// Pet.cc
#include "Pet.h"
Pet::Pet(const std::string &name) : name_(name) {
}
void Pet::setName(const std::string &name) {
name_ = name;
}
const std::string &Pet::getName() const {
return name_;
}
Dog::Dog(const std::string &name) : Pet(name) {
}
std::string Dog::bark() const {
return "Woof!";
}
添加绑定:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
#include <memory>
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<const std::string&>());
pet.def("getName", &Pet::getName);
pet.def("setName", &Pet::setName);
pet.def("__repr__", [](const Pet &a) {
return "<example.Pet named '" + a.getName() + "'>";
});
py::class_<Dog, Pet> dog (m, "Dog");
dog.def(py::init<const std::string&>());
dog.def("bark", &Dog::bark);
m.def("pet_store", []() {
return std::unique_ptr<Pet>(new Dog("Molly"));
});
}
编译同上,测试如下:
>> python3
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> dog = example.pet_store()
>>> dog.bark()
'Woof!'
给定一个指向多态的基类,pybind11能自动向下转换得到实际的派生类型。
3.6 重载方法
// Pet.h
#pragma once
#include <string>
class Pet {
public:
Pet() = default;
~Pet() = default;
void set(int age);
void set(const std::string &name);
const std::string &getName() const;
int getAge() const;
private:
int age_;
std::string name_;
};
// Pet.cc
#include "Pet.h"
void Pet::set(int age) {
age_ = age;
}
void Pet::set(const std::string &name) {
name_ = name;
}
const std::string &Pet::getName() const {
return name_;
}
int Pet::getAge() const {
return age_;
}
添加绑定:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
#include <memory>
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<>());
pet.def("set", py::overload_cast<int>(&Pet::set), "set pet age");
pet.def("set", py::overload_cast<const std::string&>(&Pet::set), "set pet name");
pet.def("getName", &Pet::getName, "get pet name");
pet.def("getAge", &Pet::getAge, "get pet age");
}
注意这里需要C++14以上标准才能支持:
>> g++ -O3 -Wall -shared -std=c++14 -fPIC $(python3 -m pybind11 --includes) example.cc Pet.cc -o example$(python3-config --extension-suffix)
测试:
>> python
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet()
>>> pet.set('Lucy')
>>> pet.set(20)
>>> pet.getName()
'Lucy'
>>> pet.getAge()
20
如果想使用C++11标准,需要将绑定修改如下:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
#include <memory>
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<>());
// pet.def("set", py::overload_cast<int>(&Pet::set), "set pet age");
// pet.def("set", py::overload_cast<const std::string&>(&Pet::set), "set pet name");
pet.def("set", static_cast<void (Pet::*)(int)>(&Pet::set), "set pet age");
pet.def("set", static_cast<void (Pet::*)(const std::string &)>(&Pet::set), "set pet name");
pet.def("getName", &Pet::getName, "get pet name");
pet.def("getAge", &Pet::getAge, "get pet age");
}
编译测试:
>> g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cc Pet.cc -o example$(python3-config --extension-suffix)
>> python
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.set("Marry")
很显然,上面这种方法使用起来更加简单方便。当然,你也可以自己封装一下:
// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
#include <memory>
namespace py = pybind11;
template <typename... Args>
using overload_cast_ = py::detail::overload_cast_impl<Args...>;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<>());
pet.def("set", overload_cast_<int>()(&Pet::set), "set pet age");
pet.def("set", overload_cast_<const std::string&>()(&Pet::set), "set pet name");
pet.def("getName", &Pet::getName, "get pet name");
pet.def("getAge", &Pet::getAge, "get pet age");
}
然后编译,测试一下,结果一样。
3.7 枚举和内部类型
假设类中包含内部枚举类型:
// Pet.h
#pragma once
#include <string>
class Pet {
public:
enum Kind {
Dog = 0,
Cat
};
Pet() = default;
~Pet() = default;
void set(int age);
void set(const std::string &name);
void set(Kind kind);
const std::string &getName() const;
int getAge() const;
Kind getKind() const;
private:
int age_;
std::string name_;
Kind kind_;
};
// Pet.cc
#include "Pet.h"
void Pet::set(int age) {
age_ = age;
}
void Pet::set(const std::string &name) {
name_ = name;
}
void Pet::set(Kind kind) {
kind_ = kind;
}
const std::string &Pet::getName() const {
return name_;
}
int Pet::getAge() const {
return age_;
}
Pet::Kind Pet::getKind() const {
return kind_;
}
对应绑定:
.// example.cc
#include <pybind11/pybind11.h>
#include "Pet.h"
namespace py = pybind11;
template <typename... Args>
using overload_cast_ = py::detail::overload_cast_impl<Args...>;
PYBIND11_MODULE(example, m) {
py::class_<Pet> pet (m, "Pet", py::dynamic_attr());
pet.def(py::init<>());
pet.def("set", overload_cast_<int>()(&Pet::set), "set pet age");
pet.def("set", overload_cast_<const std::string&>()(&Pet::set), "set pet name");
pet.def("set", overload_cast_<Pet::Kind>()(&Pet::set), "set pet kind");
pet.def("getName", &Pet::getName, "get pet name");
pet.def("getAge", &Pet::getAge, "get pet age");
pet.def("getKind", &Pet::getKind, "get pet kind");
py::enum_<Pet::Kind> kind (pet, "Kind");
kind.value("Dog", Pet::Kind::Dog);
kind.value("Cat", Pet::Kind::Cat);
kind.export_values();
}
编译测试:
>> g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cc Pet.cc -o example$(python3-config --extension-suffix)
>> python
Python 3.8.10 (default, Sep 11 2024, 16:02:53)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> pet = example.Pet()
>>> pet.set(pet.Dog)
>>> pet.set("Mirror")
>>> pet.set(30)
>>> pet.getKind()
<Kind.Dog: 0>
>>> pet.getName()
'Mirror'
>>> pet.getAge()
30
4.CMake工程化
- 懒得写了,可以直接参考我这边写的学习工程:PyBinder