本文主要记录官方文档中 EXCEPTIONS 和 TYPE CONVERSIONS 一章的学习笔记。
当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
异常。
当上述的异常转换不能满足我们需求时,我们可以注册一个自定义的C++到Python的异常转换。使用下面的方法:
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.1节](##1.1 C++内置异常到Python异常的转换)是C++异常到Python异常的转换,这一节为Python异常到C++异常的转换。
在Python中抛出的异常 | 作为C++异常类型抛出 |
---|---|
Any Python Exception |
pybind11::error_already_set |
下面例子演示了如何在C++侧处理捕获到的Python异常。
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;
}
}
在C++中使用原生的Python类型(如list或tuple等),我们有两种方式。第一,通过包装,将原生Python类型通过py::object
派生包装器包装,在C++中撕掉包装后再使用,其核心仍然是一个Python对象。示例如下:
namespace py = pybind11;
void print_list(py::list my_list) {
for (auto item : my_list)
std::cout << item << " ";
}
>>> print_list([1, 2, 3])
1 2 3
其中
py::list
便是Python原生类型list
在C++中的包装类型。
第二种方式是通过类型转换,将原生Python类型转换成原生C++类型后再在C++侧使用,即两侧都使用各自的原生类型。示例如下:
void print_vector(const std::vector<int> &v) {
for (auto item : v)
std::cout << item << "\n";
}
>>> 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 |
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 |
Complex numbers | pybind11/complex.h |
std::array |
STL static array | pybind11/stl.h |
std::vector |
STL dynamic array | pybind11/stl.h |
std::deque |
STL double-ended queue | pybind11/stl.h |
std::valarray |
STL value array | pybind11/stl.h |
std::list |
STL linked list | pybind11/stl.h |
std::map |
STL ordered map | pybind11/stl.h |
std::unordered_map |
STL unordered map | pybind11/stl.h |
std::set |
STL ordered set | pybind11/stl.h |
std::unordered_set |
STL unordered set | pybind11/stl.h |
std::optional |
STL optional type (C++17) | pybind11/stl.h |
std::experimental::optional |
STL optional type (exp.) | pybind11/stl.h |
std::variant<...> |
Type-safe union (C++17) | pybind11/stl.h |
std::filesystem::path |
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 |
注意:上述的内置转换都是基于数据拷贝的。这对小型的不变的类型相当友好,对于大型数据结构则相当昂贵。这可以通过自定义包装类型重载自动转换来解决。
包含头文件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 |
这些类型任意嵌套都是可以自动转换的。
有时,我们需要创建STL容器的Python绑定,使在Python中将STL容器作为对象进行使用。
// 必须包含的头文件
#include
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对象的内容永远不会被检查或提取,因此它们可以通过引用传递。这样一来避免对大型列表进行拷贝操作带来的耗时。
如果一个将函数对象作为函数的参数,那么设计Python中函数和C++函数对象之间的转换。有一如下示例函数,接收一函数对象,返回一函数对象:
#include
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);
}