Skip to content

第12章 异常处理

pybind11 提供了强大的异常处理机制,实现了 C++ 异常与 Python 异常之间的自动双向转换。本章将详细讲解各种异常处理场景。

12.1 C++ 异常自动转换为 Python 异常

Section titled “12.1 C++ 异常自动转换为 Python 异常”

当 C++ 代码抛出异常时,pybind11 会自动将其转换为对应的 Python 异常。这一过程对调用者完全透明。

#include <pybind11/pybind11.h>
#include <stdexcept>
namespace py = pybind11;
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("division by zero");
}
return a / b;
}
PYBIND11_MODULE(exception_module, m) {
m.def("divide", &divide);
}
>>> import exception_module
>>> exception_module.divide(10, 0)
Traceback (most recent call last):
RuntimeError: division by zero

pybind11 自动捕获 C++ 异常并将其转换为 Python 异常,异常消息也会被正确传递。

pybind11 定义了从 C++ 异常类型到 Python 异常类型的默认映射:

C++ 异常类型Python 异常类型
std::exceptionRuntimeError
std::bad_allocMemoryError
std::invalid_argumentValueError
std::out_of_rangeIndexError
std::logic_errorRuntimeError
std::runtime_errorRuntimeError
#include <pybind11/pybind11.h>
#include <stdexcept>
#include <vector>
namespace py = pybind11;
class Matrix {
public:
Matrix(size_t rows, size_t cols) : data(rows * cols) {
if (rows == 0 || cols == 0) {
throw std::invalid_argument("matrix dimensions must be non-zero");
}
if (rows > 10000 || cols > 10000) {
throw std::out_of_range("matrix dimensions too large");
}
}
private:
std::vector<double> data;
};
PYBIND11_MODULE(matrix_module, m) {
py::class_<Matrix>(m, "Matrix")
.def(py::init<size_t, size_t>());
}
>>> from matrix_module import Matrix
>>> Matrix(0, 5)
Traceback (most recent call last):
ValueError: matrix dimensions must be non-zero
>>> Matrix(20000, 20000)
Traceback (most recent call last):
IndexError: matrix dimensions too large

可以使用 py::register_exception 将自定义 C++ 异常类注册为 Python 异常。

#include <pybind11/pybind11.h>
#include <exception>
namespace py = pybind11;
// 自定义异常类
class my_error : public std::exception {
public:
explicit my_error(const char* msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
private:
std::string message;
};
PYBIND11_MODULE(custom_exception_module, m) {
// 注册自定义异常,将 C++ my_error 映射到 Python MyError
py::register_exception<my_error>(m, "MyError");
}
>>> import custom_exception_module
>>> try:
... raise custom_exception_module.MyError("custom error occurred")
... except custom_exception_module.MyError as e:
... print(f"Caught: {e}")
Caught: custom error occurred

关键洞察:py::register_exception 的第二个参数指定了 Python 中的异常名称,可以与 C++ 类名不同。

异常消息在跨语言边界传递时需要特别注意编码问题。

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <stdexcept>
namespace py = pybind11;
void process_data(const std::string& input) {
if (input.empty()) {
throw std::invalid_argument("input string is empty");
}
if (input.length() > 1000) {
throw std::runtime_error("input string exceeds maximum length of 1000");
}
// 处理逻辑...
}
PYBIND11_MODULE(exception_msg_module, m) {
m.def("process_data", &process_data);
}
>>> exception_msg_module.process_data("")
Traceback (most recent call last):
ValueError: input string is empty
>>> exception_msg_module.process_data("x" * 1001)
Traceback (most recent call last):
RuntimeError: input string exceeds maximum length of 1000

当 C++ 中有未捕获的异常时,pybind11 会将其转换为 Python 的 RuntimeError。

#include <pybind11/pybind11.h>
namespace py = pybind11;
// 故意不捕获异常
void risky_operation(int value) {
// 如果 value < 0,会抛出异常但不被捕获
if (value < 0) {
throw -1; // int 类型的异常,非 std::exception 子类
}
}
PYBIND11_MODULE(uncaught_exception_module, m) {
m.def("risky_operation", &risky_operation);
}
>>> uncaught_exception_module.risky_operation(-1)
Traceback (most recent call last):
RuntimeError: Silenced or unknown C++ exception

关键洞察:如果 C++ 抛出的异常不是 std::exception 的派生类,pybind11 可能无法提取有意义的异常消息,此时 Python 端只会看到 “Silenced or unknown C++ exception”。

使用 py::error_already_set 来检查和重新抛出 Python 异常。

#include <pybind11/pybind11.h>
#include <iostream>
namespace py = pybind11;
void handle_python_exception() {
try {
// 调用可能抛出 Python 异常的代码
py::object result = py::eval("raise ValueError('test error')");
} catch (const py::error_already_set& e) {
// 检查异常类型
if (e.matches(PyExc_ValueError)) {
std::cerr << "Caught ValueError: " << e.what() << std::endl;
} else if (e.matches(PyExc_TypeError)) {
std::cerr << "Caught TypeError: " << e.what() << std::endl;
}
// 重新抛出给 Python
e.restore();
throw;
}
}
PYBIND11_MODULE(python_to_cpp_module, m) {
m.def("handle_exception", &handle_python_exception);
}
>>> import python_to_cpp_module
>>> try:
... python_to_cpp_module.handle_exception()
... except ValueError as e:
... print(f"Re-raised ValueError: {e}")
Re-raised ValueError: test error

关键洞察:py::error_already_set 用于在 C++ 代码中捕获 Python 异常。e.matches() 用于检查异常类型,e.restore() 用于重新抛出异常给 Python 调用者。