使用pybind11在Python中调用C++对象时的类型转换报错问题
最近在做pybind11嵌入Python的项目时,踩了个实实在在的坑:编译全程无报错,一运行就直接崩溃,卡在py::cast(car1)这行,抛出pybind11::cast_error,错误信息是'make_tuple(): unable to convert argument of type 'object' to Python object'。折腾了半天终于搞懂了原因,下面把问题代码、分析和解决方案分享给大家。
问题代码回顾
main.cpp
// Embeding the interpreter into c++ #include <pybind11/embed.h> #include <iostream> #include <string> namespace py = pybind11; class Vehiclee { public: int vehicle_id; std::string vehicle_name; std::string vehicle_color; void printname() { std::cout << "Vehicle id is: " << vehicle_id; std::cout << "Vehicle name is: " << vehicle_name; std::cout << "Vehicle color is: " << vehicle_color; } }; int main() { py::scoped_interpreter python; py::module simpleFuncs = py::module::import("simpleFuncs"); Vehiclee car1; car1.vehicle_id = 1234; car1.vehicle_name = "VehicleName"; car1.vehicle_color = "red"; py::object car2 = py::cast(car1); // <-- 崩溃触发点 simpleFuncs.attr("simplePrint")(car2); return 0; }
simpleFuncs.py
def simplePrint(argument): print(argument)
CMakeLists.txt
cmake_minimum_required(VERSION 3.0) project(wrapper) add_subdirectory(pybind11) add_executable(wrapper main.cpp) target_link_libraries(wrapper PRIVATE pybind11::embed)
问题根源
说白了就是pybind11根本不知道怎么把你的自定义C++类Vehiclee转换成Python对象。它只自带了基础类型(int、string、STL容器等)的转换规则,对于你自己写的类,必须主动给它一套映射规则——也就是绑定类的属性和方法,告诉它如何处理这个自定义类型。
解决方案:给自定义类添加pybind11绑定
我们需要在C++代码里添加一个嵌入模式专用的模块绑定,用PYBIND11_EMBEDDED_MODULE宏来定义,把Vehiclee类的属性和方法暴露给Python。
修改后的main.cpp
// Embeding the interpreter into c++ #include <pybind11/embed.h> #include <iostream> #include <string> namespace py = pybind11; class Vehiclee { public: int vehicle_id; std::string vehicle_name; std::string vehicle_color; void printname() { std::cout << "Vehicle id is: " << vehicle_id << "\n"; std::cout << "Vehicle name is: " << vehicle_name << "\n"; std::cout << "Vehicle color is: " << vehicle_color << "\n"; } }; // 关键新增:定义嵌入模式的模块,绑定Vehiclee类 PYBIND11_EMBEDDED_MODULE(cpp_classes, m) { m.doc() = "Embedded module exposing C++ classes to Python"; // 模块说明 py::class_<Vehiclee>(m, "Vehiclee") .def_readwrite("vehicle_id", &Vehiclee::vehicle_id) // 暴露公共属性 .def_readwrite("vehicle_name", &Vehiclee::vehicle_name) .def_readwrite("vehicle_color", &Vehiclee::vehicle_color) .def("printname", &Vehiclee::printname); // 暴露成员方法 } int main() { py::scoped_interpreter python; // 可选:导入我们的C++绑定模块(如果Python代码要直接实例化Vehiclee的话需要) py::module cpp_classes = py::module::import("cpp_classes"); py::module simpleFuncs = py::module::import("simpleFuncs"); Vehiclee car1; car1.vehicle_id = 1234; car1.vehicle_name = "VehicleName"; car1.vehicle_color = "red"; // 现在可以直接传递C++对象,pybind11自动处理转换,不需要手动cast simpleFuncs.attr("simplePrint")(car1); // 手动转成py::object也完全没问题了 py::object car2 = py::cast(car1); simpleFuncs.attr("simplePrint")(car2); return 0; }
升级simpleFuncs.py(测试属性访问)
既然绑定了属性,我们可以在Python里直接访问C++对象的成员:
def simplePrint(argument): print("Received C++ object:", argument) # 直接访问C++对象的属性 print("Vehicle ID:", argument.vehicle_id) print("Vehicle Name:", argument.vehicle_name) print("Vehicle Color:", argument.vehicle_color) # 调用C++对象的成员方法 argument.printname()
编译运行
还是用原来的步骤:
git clone https://github.com/pybind/pybind11 cmake . make ./wrapper
现在运行就不会崩溃了,还能看到Python打印出C对象的属性,以及C方法执行的输出。
总结
不管是pybind11的扩展模式(写Python模块)还是嵌入模式(C调用Python),**自定义C类必须先通过py::class_绑定,pybind11才能处理它和Python对象之间的转换**。没有绑定的自定义类型,pybind11根本不知道怎么序列化/反序列化,自然会抛出转换错误。
内容的提问来源于stack exchange,提问作者Blind0ne

