14. 杂项
14.1 关于便利宏的说明
pybind11提供了一些便利宏如PYBIND11_DECLARE_HOLDER_TYPE()
和PYBIND11_OVERRIDE_*
。由于这些宏只是在预处理中计算(预处理程序没有类型的概念),它们会被模板参数中的逗号搞混。如:
PYBIND11_OVERRIDE(MyReturnType<T1, T2>, Class<T3, T4>, func)
预处理器会将其解释为5个参数(逗号分隔),而不是3个。有两种方法可以处理这个问题:使用类型别名,或者使用PYBIND11_TYPE
包裹类型。
// Version 1: using a type alias
using ReturnType = MyReturnType<T1, T2>;
using ClassType = Class<T3, T4>;
PYBIND11_OVERRIDE(ReturnType, ClassType, func);
// Version 2: using the PYBIND11_TYPE macro:
PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType<T1, T2>),
PYBIND11_TYPE(Class<T3, T4>), func)
PYBIND11_MAKE_OPAQUE
宏不需要上述解决方案。
14.2 全局解释器锁(GIL)
在Python中调用C++函数时,默认会持有GIL。gil_scoped_release
和gil_scoped_acquire
可以方便地在函数体中释放和获取GIL。这样长时间运行的C++代码可以通过Python线程实现并行化。示例如下:
class PyAnimal : public Animal {
public:
/* Inherit the constructors */
using Animal::Animal;
/* Trampoline (need one for each virtual function) */
std::string go(int n_times) {
/* Acquire GIL before calling Python code */
py::gil_scoped_acquire acquire;
PYBIND11_OVERRIDE_PURE(
std::string, /* Return type */
Animal, /* Parent class */
go, /* Name of function */
n_times /* Argument(s) */
);
}
};
PYBIND11_MODULE(example, m) {
py::class_<Animal, PyAnimal> animal(m, "Animal");
animal
.def(py::init<>())
.def("go", &Animal::go);
py::class_<Dog>(m, "Dog", animal)
.def(py::init<>());
m.def("call_go", [](Animal *animal) -> std::string {
/* Release GIL before calling into (potentially long-running) C++ code */
py::gil_scoped_release release;
return call_go(animal);
});
}
我们可以使用call_guard
策略来简化call_go
的封装:
m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());
14.3 通过多个模块来划分代码
通常我们可以直接将绑定代码分隔到多个模块中,即便模块引用的类型在其他模块中定义。有个例外场景,就是当前扩展的类型定义在其他模块中,参见下面的例子:
py::class_<Pet> pet(m, "Pet");
pet.def(py::init<const std::string &>())
.def_readwrite("name", &Pet::name);
py::class_<Dog>(m, "Dog", pet /* <- specify parent */)
.def(py::init<const std::string &>())
.def("bark", &Dog::bark);
假设Pet
类的绑定定义在basic
模块中,而Dog
绑定定义在其他模块。在class_<Dog>
中明确与Pet
类的继承关系时需要知道Pet
,问题是在其他模块定义的Pet
不再对Dog
可见。我们可以这样处理:
py::object pet = (py::object) py::module_::import("basic").attr("Pet");
py::class_<Dog>(m, "Dog", pet)
.def(py::init<const std::string &>())
.def("bark", &Dog::bark);
或者,你可以将基类作为模板参数给class_
,让pybind11自动查找到相应的Python类型。但也需要调用一次import
函数,确保basic
模块的绑定代码已经执行。
py::module_::import("basic");
py::class_<Dog, Pet>(m, "Dog")
.def(py::init<const std::string &>())
.def("bark", &Dog::bark);
如果存在循环依赖时,上述两种方法都将失效。
注意,pybind11代码在编译时会默认隐藏符号的可见性(如通过GCC/Clang的-fvisibility=hidden
标识),这会干扰访问在其他模块定义的类型的能力。这需要通过手动导出需要被其他模块访问的类型,像这样:
class PYBIND11_EXPORT Dog : public Animal {
...
};
在运行时也可以共享任意的C++对象,尽管很少用到该特性。使用capsule机制在模块间共享内部库数据,可以用来存储、修改、访问用户自定义数据。注意,一个模块能够看到其他模块的数据,仅在他们使用相同的pybind11版本编译时才能实现。参考下面的例子:
auto data = reinterpret_cast<MyData *>(py::get_shared_data("mydata"));
if (!data)
data = static_cast<MyData *>(py::set_shared_data("mydata", new MyData(42)));
如果在几个单独编译的扩展模块中使用了上述代码段,第一个导入的模块将创建MyData
实例,并和指针联系起来。后续导入的模块就可以访问该指针指向的数据了。
14.4 模块析构
pybind11没有提供明确的机制在模块析构时调用清理代码。在少数需要该功能的场景下,可以使用Python capsules或析构回调函数的弱引用来模仿它。
auto cleanup_callback = []() {
// perform cleanup here -- this function is called with the GIL held
};
m.add_object("_cleanup", py::capsule(cleanup_callback));
该方法一个潜在地缺陷是,在cleanup函数调用时,模块公开的类实例可能仍存活着(这是否可以接受通常取决于应用程序)。
或者,我们可以将capsule存储在类型对象中,确保它不会在回收该类型的所有实例之前被调用:
auto cleanup_callback = []() { /* ... */ };
m.attr("BaseClass").attr("_cleanup") = py::capsule(cleanup_callback);
上面的方法都在Python中暴露了一个_cleanup
的危险属性,从API的角度来看,这种做法并不受欢迎(Python过早的显式调用它可能会导致未定义行为)。这可以通过使用cleanup函数回调的弱引用来规避。
// Register a callback function that is invoked when the BaseClass object is collected
py::cpp_function cleanup_callback(
[](py::handle weakref) {
// perform cleanup here -- this function is called with the GIL held
weakref.dec_ref(); // release weak reference
}
);
// Create a weak reference with a cleanup callback and initially leak it
(void) py::weakref(m.attr("BaseClass"), cleanup_callback).release();