第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", ÷);}>>> import exception_module>>> exception_module.divide(10, 0)Traceback (most recent call last):RuntimeError: division by zeropybind11 自动捕获 C++ 异常并将其转换为 Python 异常,异常消息也会被正确传递。
12.2 异常类型映射
Section titled “12.2 异常类型映射”pybind11 定义了从 C++ 异常类型到 Python 异常类型的默认映射:
| C++ 异常类型 | Python 异常类型 |
|---|---|
std::exception | RuntimeError |
std::bad_alloc | MemoryError |
std::invalid_argument | ValueError |
std::out_of_range | IndexError |
std::logic_error | RuntimeError |
std::runtime_error | RuntimeError |
#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 large12.3 自定义异常类注册
Section titled “12.3 自定义异常类注册”可以使用 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++ 类名不同。
12.4 异常消息传递
Section titled “12.4 异常消息传递”异常消息在跨语言边界传递时需要特别注意编码问题。
#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 100012.5 未捕获异常处理
Section titled “12.5 未捕获异常处理”当 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”。
12.6 Python 异常转换为 C++
Section titled “12.6 Python 异常转换为 C++”使用 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 调用者。