本文主要记录官方文档中 FUNCTIONS 一章的学习笔记。
对于C++ 函数的Python绑定,在前面的学习中已经有所涉及了,详见:pybind11学习 | 迈出第一步。本文主要是记录一些更加深入的知识。
文章目录
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 | 返回值的生命周期与父对象的生命周期相绑定,即被调用函数或属性的this 或self 对象。这种策略与reference策略类似,但附加了keep_alive<0, 1> 调用策略保证返回值还被Python引用时,其父对象就不会被垃圾回收掉。这是由def_property 、def_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
时,调用策略将什么都不做。
通过该策略,我们可以实现将被包含对象的声明周期绑定到包含对象上,示例如下:
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应用于整个函数调用。示例如下:
m.def("foo", foo, py::call_guard<T>());
call_guard
类模板源码声明如下:
/** \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
中声明默认参数前,先将该自定义类型进行绑定。
py::class_<SomeType>("SomeType")
//...
py::class_<MyClass>("MyClass").def("myFunction", py::arg("arg") = SomeType(123));
使用py::arg_v
给默认参数手动添加方便阅读的注释。
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg_v("arg", SomeType(123), "SomeType(123)"));
使用空指针作为默认参数:
py::class_<MyClass>("MyClass")
.def("myFunction", py::arg("arg") = static_cast<SomeType *>(nullptr));
4 Keyword-only参数
Python3引入Keyword-only参数语法。用法参见《Fluent Python》笔记 | 函数对象和装饰器中仅限关键字参数部分。
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
对象来实现相同的功能:
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()
来提供相同的功能:
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()
方法指定。
m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert());
7 允许/禁止None参数
使用py::arg
对象的.none(bool)
方法来显式地允许或禁止在Python中传入的该参数为None
。在不显式指定的情况下,默认支持传递None
。