MirrorYuChen
MirrorYuChen
Published on 2024-11-12 / 42 Visits
0
0

Pybind11学习笔记

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。

​ pybind11header-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()方法来暴露相关字段,但是这里需要显式的调用 settergetter函数:

// 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

5.参考资料


Comment