python中bind的用法_PyBind11:基本用法和底层实现

PyBind11底层其实就是CPython的各个API调用,但使用C++做了良好的封装。

PyBind11中大量应用了现代C++技巧,如变长参数模板(variadic template)、lambda表达式,同时也使用了一些传统的C++技巧和设计模式,如奇异递归模板模式(CRTP,Curiously Recurring Template Pattern)。

下面的所有说明遵循官方的惯例,使用如下头文件和命名空间别名:

#include namespace py = pybind11;

模块入口函数

导入模块:

PYBIND11_MODULE(test, m) {

m.doc() = "PyBind11 Test";

m.def("add", [](int i, int j) { return i + j; });

}

该宏PYBIND11_MODULE定义了函数PYBIND11_PLUGIN_IMPL(name):

#define PYBIND11_MODULE(name, variable) \static void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module&); \PYBIND11_PLUGIN_IMPL(name) { \PYBIND11_CHECK_PYTHON_VERSION \auto m = pybind11::module(pybind11_init_, name)(m); \try { \PYBIND11_CONCAT(pybind11_init_, name)(m); \return m.ptr(); \} PYBIND11_CATCH_INIT_EXCEPTIONS \} \void PYBIND11_CONCAT(pybind11_init_, name)(pybind11::module& variable)

这个函数名会宏展开为extern "C" PyObject* PyInit_##name()

#define PYBIND11_PLUGIN_IMPL(name) \extern "C" PYBIND11_EXPORT PyObject* PyInit_##name(); \extern "C" PYBIND11_EXPORT PyObject* PyInit_##name()

而CPython需要导入的模块实现的即这个函数。

C++函数的封装

C++函数被封装在类cpp_function中,它允许接受的C++“函数”有:普通函数指针(形如R (*)(Args ...))

函数对象(亦称仿函数)

lambda表达式实际上就是仿函数的语法糖

具有R operator()(Args ...)的括号运算符重载)

类的成员函数指针(形如R (C::*)(Args ...))

类的常成员函数指针(形如R (C::*)(Args ...) const)。

函数类cpp_function

C++侧

概括来说,cpp_function使用了和std::function类似的类型擦除(type erasure)方法。在模板化的构造函数中趁着还有目标函数(被绑定的C++函数)的类型信息,完成如下变换:把类的成员函数通过lambda转化为仿函数:

Result (Class::*f)(Args ...) /* const */;

[f](/* const */ Class* c, Args... args) -> Result {

return c->f(args...);

};此后都可以按照普通函数来调用。实现一个普通函数impl将std::vector中存储的参数转发给被绑定的C++函数

最终将上述函数impl转化为PyObject备用

CPython侧

PyBind11使用CPython的API函数PyCFunction_NewEx创建Python函数PyCFunctionObject;使用PyInstanceMethod_New(Python 3)或者PyMethod_New(Python 2)来创建一个PyObject。

辅助函数def

类module的成员函数def就接受一个函数名,一个C++“函数”,以及数个额外属性:

template

module& module::def(const char* name_, Func&& f, const Extra& ... extra);

其中函数f会被完美转发给cpp_function的构造函数。返回值为module&用于支持连续的def调用。

额外属性可以是类型为arg的对象,用于表示Python的具名参数。PyBind11定义了用户自定义字面量constexpr arg operator"" _a(const char* name, size_t length);这允许我们直接将调用py::arg("i")写为字面量"i"_a。此外,arg重载了赋值运算符,可以通过"i"_a = 1表示一个具有默认形参1的具名参数i。

最终,函数f通过CPython的PyModule_AddObject函数注册到Python侧,但并不是作为Python函数,而是作为一个可调用对象(实现了__call__方法的对象)。

Python属性

函数attr接受一个const char*(空字符结尾的C字符串)或者一个句柄handle(持有另一个Python对象py::object),这个参数作为Python中访问它所使用的键(key)。attr返回一个可赋值的obj_attr_accessor或str_attr_accessor对象,对其赋值则会指定该属性名对应的Python对象。

// 直接赋值m.attr("the_answer") = 42;

// 或者,使用 py::cast 转换m.attr("what") = py::cast("World");

这将最终在PyBind11内部转化为对CPython的API函数PyObject_SetAttr和PyObject_SetAttrString的调用。

C++类的封装

首先创建一个py::class_类型的变量,然后使用def和attr向类中添加属性和方法。py::class_最终继承自py::object,因此attr函数的描述可以参考上面的说明。成员函数def有新的定义,并且没有显式using父类的def,因此父类的def被隐藏。

这里只讨论最常用的def版本。它的函数签名仍然和上面描述的一致,接受一个函数名,一个C++”函数“,以及一组额外属性。不同的是,这里的处理方法是将这个cpp_function作为该类的一个属性(对于模块是直接添加为模块的对象),不过相同的是在Python一侧看来它都是一个可调用对象。

鸣谢:本文及其余博文使用Jekyll生成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值