pybind11学习 函数的绑定

本文主要记录官方文档中 FUNCTIONS 一章的学习笔记。

对于C++ 函数的Python绑定,在前面的学习中已经有所涉及了,详见:pybind11学习 | 迈出第一步。本文主要是记录一些更加深入的知识。

[TOC]

1 返回值策略

Python和C++在内存管理和对象生命周期管理上存在本质的区别。为此,pybind11提供了一些返回值策略来确定由哪方管理资源。pybind11在绑定C++函数时,一个有7个返回值策略,都在py::return_value_policy(py为pybind11的别名)枚举类型中。这些策略通过model_::def()(模块函数)和class_::def()(类成员方法)来指定,默认策略为return_value_policy::automatic

返回值策略 描述
return_value_policy::take_ownership 引用现有对象(不创建一个新对象),并获取所有权。在引用计数为0时,Pyhton将调用析构函数和delete操作销毁对象。
return_value_policy::copy 拷贝返回值,这样Python将拥有拷贝的对象。该策略相对来说比较安全,因为两个实例的生命周期是分离的。
return_value_policy::move 使用std::move来移动返回值的内容到新实例,新实例的所有权在Python。该策略相对来说比较安全,因为两个实例的生命周期是分离的。
return_value_policy::reference 引用现有对象,但不拥有所有权。C++侧负责该对象的生命周期管理,并在对象不再被使用时负责析构它。注意:当Python侧还在使用引用的对象时,C++侧删除对象将导致未定义行为。
return_value_policy::reference_internal 返回值的生命周期与父对象的生命周期相绑定,即被调用函数或属性的thisself对象。这种策略与reference策略类似,但附加了keep_alive<0, 1>调用策略保证返回值还被Python引用时,其父对象就不会被垃圾回收掉。这是由def_propertydef_readwrite创建的属性getter方法的默认返回值策略。
return_value_policy::automatic 当返回值是指针时,该策略使用return_value_policy::take_ownership。反之对左值和右值引用使用return_value_policy::copy
return_value_policy::automatic_reference 和上面一样,但是当返回值是指针时,使用return_value_policy::reference策略。这是在C++代码手动调用Python函数和使用pybind11/stl.h中的casters时的默认转换策略。你可能不需要显式地使用该策略。
  • 代码使用无效的返回值策略将导致未初始化内存或多次释放数据结构,这将导致难以调试的、不确定的问题和段错误。
  • 如果函数返回值为智能指针,可以不必指定返回值策略。

2 调用策略

通过指定调用策略可以表明参数间的依赖关系,确保函数调用的稳定性

2.1 keep alive

当一个C++容器对象包含另一个C++对象时,我们需要使用该策略。keep_alive<Nurse, Patient>表明在索引Nurse被回收前,索引Patient应该被keep alive。0表示返回值,1及以上表示参数索引。1表示隐含的参数this指针,而常规参数索引从2开始。当Nurse的值在运行前被检测到为None时,调用策略将什么都不做。

通过该策略,我们可以实现将被包含对象的声明周期绑定到包含对象上,示例如下:

1
2
3
py::class_<List>(m, "List").def("append", &List::append, py::keep_alive<1, 2>());

py::class_<Nurse>(m, "Nurse").def(py::init<Patient &>(), py::keep_alive<1, 2>());

2.2 call guard

call_guard<T>策略允许任意T类型的scope guard应用于整个函数调用。示例如下:

1
m.def("foo", foo, py::call_guard<T>());

call_guard类模板源码声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** \rst
A call policy which places one or more guard variables (``Ts...``) around the function call.
\endrst */
template <typename... Ts>
struct call_guard;

template <>
struct call_guard<> {
using type = detail::void_type;
};

template <typename T>
struct call_guard<T> {
static_assert(std::is_default_constructible<T>::value,
"The guard type must be default constructible");

using type = T;
};

template <typename T, typename... Ts>
struct call_guard<T, Ts...> {
struct type {
T guard{}; // Compose multiple guard types with left-to-right default-constructor order
typename call_guard<Ts...>::type next{};
};
};

3 默认参数

默认参数在声明时就已经被转换为Python对象了。如果默认参数为自定义类型,需要保证在class_::def中声明默认参数前,先将该自定义类型进行绑定。

1
2
3
4
py::class_<SomeType>("SomeType")
//...

py::class_<MyClass>("MyClass").def("myFunction", py::arg("arg") = SomeType(123));

使用py::arg_v给默认参数手动添加方便阅读的注释。

1
2
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg_v("arg", SomeType(123), "SomeType(123)"));

使用空指针作为默认参数:

1
2
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg("arg") = static_cast<SomeType *>(nullptr));

4 Keyword-only参数

Python3引入Keyword-only参数语法。用法参见《Fluent Python》笔记 | 函数对象和装饰器仅限关键字参数部分。

1
2
3
4
5
6
7
def f(a, *, b):  # a can be positional or via keyword; b must be via keyword
pass

f(a=1, b=2) # good
f(b=2, a=1) # good
f(1, b=2) # good
f(1, 2) # TypeError: f() takes 1 positional argument but 2 were given

pybind11提供了py::kw_only对象来实现相同的功能:

1
2
m.def("f", [](int a, int b) { /* ... */ },
py::arg("a"), py::kw_only(), py::arg("b"));

注,该特性不能与py::args一起使用。

5 Positional-only参数

python3.8引入了Positional-only参数语法。即只能按位置赋值,不能通过关键字赋值。

pybind11通过py::pos_only()来提供相同的功能:

1
2
m.def("f", [](int a, int b) { /* ... */ },
py::arg("a"), py::pos_only(), py::arg("b"));

6 Non-converting参数

当对Non-converting参数进行转换(包含隐式转换)时,代码会抛出错误。

Non-converting参数通过py::arg来调用.noconvert()方法指定。

1
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());	

7 允许/禁止None参数

使用py::arg对象的.none(bool)方法来显式地允许或禁止在Python中传入的该参数为None。在不显式指定的情况下,默认支持传递None


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!