14. 杂项

14.1 关于便利宏的说明

pybind11提供了一些便利宏如PYBIND11_DECLARE_HOLDER_TYPE()PYBIND11_OVERRIDE_*。由于这些宏只是在预处理中计算(预处理程序没有类型的概念),它们会被模板参数中的逗号搞混。如:

  1. PYBIND11_OVERRIDE(MyReturnType<T1, T2>, Class<T3, T4>, func)

预处理器会将其解释为5个参数(逗号分隔),而不是3个。有两种方法可以处理这个问题:使用类型别名,或者使用PYBIND11_TYPE包裹类型。

  1. // Version 1: using a type alias
  2. using ReturnType = MyReturnType<T1, T2>;
  3. using ClassType = Class<T3, T4>;
  4. PYBIND11_OVERRIDE(ReturnType, ClassType, func);
  5. // Version 2: using the PYBIND11_TYPE macro:
  6. PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType<T1, T2>),
  7. PYBIND11_TYPE(Class<T3, T4>), func)

PYBIND11_MAKE_OPAQUE宏不需要上述解决方案。

14.2 全局解释器锁(GIL)

在Python中调用C++函数时,默认会持有GIL。gil_scoped_releasegil_scoped_acquire可以方便地在函数体中释放和获取GIL。这样长时间运行的C++代码可以通过Python线程实现并行化。示例如下:

  1. class PyAnimal : public Animal {
  2. public:
  3. /* Inherit the constructors */
  4. using Animal::Animal;
  5. /* Trampoline (need one for each virtual function) */
  6. std::string go(int n_times) {
  7. /* Acquire GIL before calling Python code */
  8. py::gil_scoped_acquire acquire;
  9. PYBIND11_OVERRIDE_PURE(
  10. std::string, /* Return type */
  11. Animal, /* Parent class */
  12. go, /* Name of function */
  13. n_times /* Argument(s) */
  14. );
  15. }
  16. };
  17. PYBIND11_MODULE(example, m) {
  18. py::class_<Animal, PyAnimal> animal(m, "Animal");
  19. animal
  20. .def(py::init<>())
  21. .def("go", &Animal::go);
  22. py::class_<Dog>(m, "Dog", animal)
  23. .def(py::init<>());
  24. m.def("call_go", [](Animal *animal) -> std::string {
  25. /* Release GIL before calling into (potentially long-running) C++ code */
  26. py::gil_scoped_release release;
  27. return call_go(animal);
  28. });
  29. }

我们可以使用call_guard策略来简化call_go的封装:

  1. m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());

14.3 通过多个模块来划分代码

通常我们可以直接将绑定代码分隔到多个模块中,即便模块引用的类型在其他模块中定义。有个例外场景,就是当前扩展的类型定义在其他模块中,参见下面的例子:

  1. py::class_<Pet> pet(m, "Pet");
  2. pet.def(py::init<const std::string &>())
  3. .def_readwrite("name", &Pet::name);
  4. py::class_<Dog>(m, "Dog", pet /* <- specify parent */)
  5. .def(py::init<const std::string &>())
  6. .def("bark", &Dog::bark);

假设Pet类的绑定定义在basic模块中,而Dog绑定定义在其他模块。在class_<Dog>中明确与Pet类的继承关系时需要知道Pet,问题是在其他模块定义的Pet不再对Dog可见。我们可以这样处理:

  1. py::object pet = (py::object) py::module_::import("basic").attr("Pet");
  2. py::class_<Dog>(m, "Dog", pet)
  3. .def(py::init<const std::string &>())
  4. .def("bark", &Dog::bark);

或者,你可以将基类作为模板参数给class_,让pybind11自动查找到相应的Python类型。但也需要调用一次import函数,确保basic模块的绑定代码已经执行。

  1. py::module_::import("basic");
  2. py::class_<Dog, Pet>(m, "Dog")
  3. .def(py::init<const std::string &>())
  4. .def("bark", &Dog::bark);

如果存在循环依赖时,上述两种方法都将失效。

注意,pybind11代码在编译时会默认隐藏符号的可见性(如通过GCC/Clang的-fvisibility=hidden标识),这会干扰访问在其他模块定义的类型的能力。这需要通过手动导出需要被其他模块访问的类型,像这样:

  1. class PYBIND11_EXPORT Dog : public Animal {
  2. ...
  3. };

在运行时也可以共享任意的C++对象,尽管很少用到该特性。使用capsule机制在模块间共享内部库数据,可以用来存储、修改、访问用户自定义数据。注意,一个模块能够看到其他模块的数据,仅在他们使用相同的pybind11版本编译时才能实现。参考下面的例子:

  1. auto data = reinterpret_cast<MyData *>(py::get_shared_data("mydata"));
  2. if (!data)
  3. data = static_cast<MyData *>(py::set_shared_data("mydata", new MyData(42)));

如果在几个单独编译的扩展模块中使用了上述代码段,第一个导入的模块将创建MyData实例,并和指针联系起来。后续导入的模块就可以访问该指针指向的数据了。

14.4 模块析构

pybind11没有提供明确的机制在模块析构时调用清理代码。在少数需要该功能的场景下,可以使用Python capsules或析构回调函数的弱引用来模仿它。

  1. auto cleanup_callback = []() {
  2. // perform cleanup here -- this function is called with the GIL held
  3. };
  4. m.add_object("_cleanup", py::capsule(cleanup_callback));

该方法一个潜在地缺陷是,在cleanup函数调用时,模块公开的类实例可能仍存活着(这是否可以接受通常取决于应用程序)。

或者,我们可以将capsule存储在类型对象中,确保它不会在回收该类型的所有实例之前被调用:

  1. auto cleanup_callback = []() { /* ... */ };
  2. m.attr("BaseClass").attr("_cleanup") = py::capsule(cleanup_callback);

上面的方法都在Python中暴露了一个_cleanup的危险属性,从API的角度来看,这种做法并不受欢迎(Python过早的显式调用它可能会导致未定义行为)。这可以通过使用cleanup函数回调的弱引用来规避。

  1. // Register a callback function that is invoked when the BaseClass object is collected
  2. py::cpp_function cleanup_callback(
  3. [](py::handle weakref) {
  4. // perform cleanup here -- this function is called with the GIL held
  5. weakref.dec_ref(); // release weak reference
  6. }
  7. );
  8. // Create a weak reference with a cleanup callback and initially leak it
  9. (void) py::weakref(m.attr("BaseClass"), cleanup_callback).release();