pybind11学习 异常转换和类型转换

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

[TOC]

一 异常转换

1.1 C++内置异常到Python异常的转换

当Python通过pybind11调用C++代码时,pybind11将捕获C++异常,并将其翻译为对应的Python异常后抛出,这样Python代码就能够处理它们。

C++抛出的异常 转换到Python的异常类型
std::exception RuntimeError
std::bad_alloc MemoryError
std::domain_error ValueError
std::invalid_argument ValueError
std::length_error ValueError
std::out_of_range IndexError
std::range_error ValueError
std::overflow_error OverflowError
pybind11::stop_iteration StopIteration
pybind11::index_error IndexError
pybind11::key_error KeyError
pybind11::value_error ValueError
pybind11::type_error TypeError
pybind11::buffer_error BufferError
pybind11::import_error ImportError
pybind11::attribute_error AttributeError
其他异常 RuntimeError

当入参不能转化为Python对象时,handle::call()将抛出pybind11::cast_error异常。

1.2 注册自定义异常转换

当上述的异常转换不能满足我们需求时,我们可以注册一个自定义的C++到Python的异常转换。使用下面的方法:

1
2
3
py::register_exception<CppExp>(module, "PyExp");		// 全局
py::register_local_exception<CppExp>(module, "PyExp"); // 模块内
py::register_exception<CppExp>(module, "PyExp", PyExc_RuntimeError); // 第三个参数可以指定异常基类,实现PyExp异常可以捕获PyExp和RuntimeError

这个调用在指定模块创建了一个名称为PyExp的Python异常,并自动将CppExp相关的异常转换为PyExp异常。

1.3 在C++中处理Python异常

[1.1节](##1.1 C++内置异常到Python异常的转换)是C++异常到Python异常的转换,这一节为Python异常到C++异常的转换。

在Python中抛出的异常 作为C++异常类型抛出
Any Python Exception pybind11::error_already_set

下面例子演示了如何在C++侧处理捕获到的Python异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
// open("missing.txt", "r")
auto file = py::module_::import("io").attr("open")("missing.txt", "r");
auto text = file.attr("read")();
file.attr("close")();
} catch (py::error_already_set &e) {
if (e.matches(PyExc_FileNotFoundError)) {
py::print("missing.txt not found");
} else if (e.matches(PyExc_PermissionError)) {
py::print("missing.txt found but not accessible");
} else {
throw;
}
}

二 类型转换

2.1 包装和类型转换

在C++中使用原生的Python类型(如list或tuple等),我们有两种方式。第一,通过包装,将原生Python类型通过py::object派生包装器包装,在C++中撕掉包装后再使用,其核心仍然是一个Python对象。示例如下:

1
2
3
4
5
6
namespace py = pybind11;

void print_list(py::list my_list) {
for (auto item : my_list)
std::cout << item << " ";
}
1
2
>>> print_list([1, 2, 3])
1 2 3

其中py::list便是Python原生类型list在C++中的包装类型。

第二种方式是通过类型转换,将原生Python类型转换成原生C++类型后再在C++侧使用,即两侧都使用各自的原生类型。示例如下:

1
2
3
4
void print_vector(const std::vector<int> &v) {
for (auto item : v)
std::cout << item << "\n";
}
1
2
>>> print_vector([1, 2, 3])
1 2 3

上面这个例子中,Python原生类型list到C++原生类型vector的转换时通过pybind11自动完成的。更多开箱即用(pybind11内置转换,不需要我们自定义类型转换)的类型转换见下表。

数据类型 描述 头文件
int8_t, uint8_t 8-bit integers pybind11/pybind11.h
int16_t, uint16_t 16-bit integers pybind11/pybind11.h
int32_t, uint32_t 32-bit integers pybind11/pybind11.h
int64_t, uint64_t 64-bit integers pybind11/pybind11.h
ssize_t, size_t Platform-dependent size pybind11/pybind11.h
float, double Floating point types pybind11/pybind11.h
bool Two-state Boolean type pybind11/pybind11.h
char Character literal pybind11/pybind11.h
char16_t UTF-16 character literal pybind11/pybind11.h
char32_t UTF-32 character literal pybind11/pybind11.h
wchar_t Wide character literal pybind11/pybind11.h
const char * UTF-8 string literal pybind11/pybind11.h
const char16_t * UTF-16 string literal pybind11/pybind11.h
const char32_t * UTF-32 string literal pybind11/pybind11.h
const wchar_t * Wide string literal pybind11/pybind11.h
std::string STL dynamic UTF-8 string pybind11/pybind11.h
std::u16string STL dynamic UTF-16 string pybind11/pybind11.h
std::u32string STL dynamic UTF-32 string pybind11/pybind11.h
std::wstring STL dynamic wide string pybind11/pybind11.h
std::string_view, std::u16string_view, etc. STL C++17 string views pybind11/pybind11.h
std::pair<T1, T2> Pair of two custom types pybind11/pybind11.h
std::tuple<...> Arbitrary tuple of types pybind11/pybind11.h
std::reference_wrapper<...> Reference type wrapper pybind11/pybind11.h
std::complex<T> Complex numbers pybind11/complex.h
std::array<T, Size> STL static array pybind11/stl.h
std::vector<T> STL dynamic array pybind11/stl.h
std::deque<T> STL double-ended queue pybind11/stl.h
std::valarray<T> STL value array pybind11/stl.h
std::list<T> STL linked list pybind11/stl.h
std::map<T1, T2> STL ordered map pybind11/stl.h
std::unordered_map<T1, T2> STL unordered map pybind11/stl.h
std::set<T> STL ordered set pybind11/stl.h
std::unordered_set<T> STL unordered set pybind11/stl.h
std::optional<T> STL optional type (C++17) pybind11/stl.h
std::experimental::optional<T> STL optional type (exp.) pybind11/stl.h
std::variant<...> Type-safe union (C++17) pybind11/stl.h
std::filesystem::path<T> STL path (C++17) 1 pybind11/stl.h
std::function<...> STL polymorphic function pybind11/functional.h
std::chrono::duration<...> STL time duration pybind11/chrono.h
std::chrono::time_point<...> STL date/time pybind11/chrono.h
Eigen::Matrix<...> Eigen: dense matrix pybind11/eigen.h
Eigen::Map<...> Eigen: mapped memory pybind11/eigen.h
Eigen::SparseMatrix<...> Eigen: sparse matrix pybind11/eigen.h

注意:上述的内置转换都是基于数据拷贝的。这对小型的不变的类型相当友好,对于大型数据结构则相当昂贵。这可以通过自定义包装类型重载自动转换来解决。

2.2 STL容器

包含头文件pybind11/stl.h,将支持如下内置转换:

C++ Python
std::vector<>/std::deque<>/std::list<>/std::array<>/std::valarray<> list
std::set<>/std::unordered_set<> set
std::map<>/std::unordered_map<> dict

这些类型任意嵌套都是可以自动转换的。

2.2.1 绑定STL容器

有时,我们需要创建STL容器的Python绑定,使在Python中将STL容器作为对象进行使用。

1
2
3
4
5
6
7
8
9
10
11
// 必须包含的头文件
#include <pybind11/stl_bind.h>

PYBIND11_MAKE_OPAQUE(std::vector<int>);
PYBIND11_MAKE_OPAQUE(std::map<std::string, double>);

// ...

// 绑定代码
py::bind_vector<std::vector<int>>(m, "VectorInt");
py::bind_map<std::map<std::string, double>>(m, "MapStringDouble");

pybind11提供了PYBIND11_MAKE_OPAQUE(T)来禁用基于模板的类型转换机制,从而使他们变得不透明(opaque)。opaque对象的内容永远不会被检查或提取,因此它们可以通过引用传递。这样一来避免对大型列表进行拷贝操作带来的耗时。

2.3 函数对象

如果一个将函数对象作为函数的参数,那么设计Python中函数和C++函数对象之间的转换。有一如下示例函数,接收一函数对象,返回一函数对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <pybind11/functional.h>

int func_arg(const std::function<int(int)> &f) {
return f(10);
}

std::function<int(int)> func_ret(const std::function<int(int)> &f) {
return [f](int i) {
return f(i) + 1;
};
}

py::cpp_function func_cpp() {
return py::cpp_function([](int i) { return i+1; },
py::arg("number"));
}

PYBIND11_MODULE(example, m) {
m.def("func_arg", &func_arg);
m.def("func_ret", &func_ret);
m.def("func_cpp", &func_cpp);
}

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