自定义C++ Data结构无法自动转为Python内置对象问题排查
问题描述
我希望将自定义的C++ Data结构自动转换为Python内置对象。以下是我的C++实现:
// data in dict template <typename... Ts> class Data { public: using variant_type = std::variant<Ts...>; Data() : _value(variant_type{}) {} template <typename T> Data(const T &value) : _value(value) {} template <typename T> Data<Ts...> &operator=(const T &V) { _value = V; return *this; } template<typename... Us> bool operator==(const Data<Us...>& other) const { if (_value.index() != other.get_variant().index()) return false; return _value == other.get_variant(); } template <typename T> T &get() { return std::get<T>(_value); } template <typename T> const T &get() const { return std::get<T>(_value); } void set(const variant_type &V) { _value = V; } template <typename T> bool is() const { return std::holds_alternative<T>(_value); } variant_type _value; const auto index() const { return _value.index(); } variant_type get_variant() const { return _value; } };
我通过pybind11定义了type_caster以实现自动转换,代码如下:
#include <pybind11/pybind11.h> #include <pybind11/stl.h> #include <pybind11/cast.h> #include "dict.h" #include <iostream> using TypedData = Data<int, std::string>; namespace py = pybind11; namespace pybind11 { namespace detail { template <> struct type_caster<TypedData> { public: PYBIND11_TYPE_CASTER(TypedData, const_name("TypedData")); static handle cast(const TypedData &d, return_value_policy, handle) { switch (d.index()) { case 0: return py::cast(d.get<int>()).inc_ref(); case 1: return py::cast(d.get<std::string>()).inc_ref(); } throw std::runtime_error("Unreachable"); } bool load(handle src, bool) { if (isinstance<py::int_>(src)) { value = TypedData(py::cast<int>(src)); } else if (isinstance<py::str>(src)) { value = TypedData(py::cast<std::string>(src)); } else return false; return true; } }; } // namespace detail } // namespace pybind11 PYBIND11_MODULE(example, m) { py::class_<TypedData>(m, "TypedData") .def(py::init<std::variant<int, std::string>>()); }
但在Python中调用时,实例化的对象并未转为预期的int或str:
In [1]: import example In [2]: a = example.TypedData(42) In [3]: a Out[3]: <example.TypedData at 0x7f6c98aaa1b0> # 期望返回int或str
请问我哪里出错了?
问题原因与解决方法
问题核心是同时注册了py::class_和type_caster,pybind11会优先使用py::class_生成的Python类,因此实例化后得到的是TypedData对象,而非自动转换后的内置类型。
要实现自动转换,需做以下修改:
1. 移除py::class_注册代码
删除模块定义中的py::class_<TypedData>(m, "TypedData").def(py::init<std::variant<int, std::string>>());,type_caster已负责处理类型转换,无需再将TypedData暴露为Python类。
2. 启用隐式转换(可选)
在type_caster的定义中,给PYBIND11_TYPE_CASTER宏添加第三个参数,指定支持隐式转换:
PYBIND11_TYPE_CASTER(TypedData, const_name("TypedData"), converter_flags::convertible);
3. 绑定C++函数实现类型转换
此时pybind11会自动处理Python与C++之间的类型转换:
- 当C++函数接收
TypedData参数时,Python传入int/string会自动转为TypedData - 当C++函数返回
TypedData时,会自动转为对应的Python内置类型
修改后的模块代码示例:
PYBIND11_MODULE(example, m) { // 示例:返回TypedData的函数,会自动转为Python内置类型 m.def("get_int_data", []() -> TypedData { return TypedData(123); }); // 示例:接收TypedData参数的函数,Python可直接传int/string m.def("process_data", [](TypedData d) { if (d.is<int>()) { return d.get<int>() * 2; } else { return d.get<std::string>() + "_processed"; } }); }
在Python中调用效果:
import example # 自动转为int res = example.get_int_data() print(res) # 输出123,类型为int # 自动将int转为TypedData处理 print(example.process_data(42)) # 输出84 # 自动将str转为TypedData处理 print(example.process_data("test")) # 输出test_processed
备选方案:保留TypedData类并模拟内置类型
如果需要在Python中显式创建TypedData对象,同时让它表现得像内置类型,可以给py::class_添加__repr__、__int__、__str__等方法:
py::class_<TypedData>(m, "TypedData") .def(py::init<int>()) .def(py::init<std::string>()) .def("__repr__", [](const TypedData& d) { if (d.is<int>()) { return std::to_string(d.get<int>()); } else { return "\"" + d.get<std::string>() + "\""; } }) .def("__int__", [](const TypedData& d) { return d.get<int>(); }) .def("__str__", [](const TypedData& d) { if (d.is<int>()) { return std::to_string(d.get<int>()); } else { return d.get<std::string>(); } });
这种方式下,对象仍为TypedData实例,但打印或转换时会表现出内置类型的行为。
内容的提问来源于stack exchange,提问作者Roy_Kid
相关产品推荐
相关产品推荐

