如何用pybind11实现C++动态类型Map的Python自动类型推导get方法?
问题背景
我有一个C的Map类,可存储不同数据类型的条目。在C中,编译期无法确定条目的数据类型,因此必须使用模板访问器显式指定类型来获取值:
auto a = map.get<int>("my-int-entry"); auto b = map.get<std::string>("my-string-entry");
这个Map在运行时知晓每个条目的实际类型,因此希望在Python中调用a = map.get('my-int-entry')(返回int类型)、b = map.get('my-string-entry')(返回string类型)时,能自动根据运行时类型调用对应的C++模板方法get<int>或get<std::string>。
请问是否可直接通过pybind11实现该功能,还是需要编写纯Python函数来调用对应的C++映射函数?
解决方案
可以直接通过pybind11实现,无需额外编写纯Python包装函数。核心思路是在C++端新增一个非模板的重载get方法,让它根据运行时类型信息分发调用对应的模板get<T>,再通过pybind11将这个非模板方法暴露给Python。
具体实现步骤
给C++ Map类添加非模板的Python适配方法
在Map类中新增一个get_py方法,该方法根据内部存储的类型信息(比如用std::type_index标记类型),分支调用对应类型的模板get<T>,并返回pybind11::object类型(pybind11可直接将其转换为Python对象)。示例代码如下:#include <pybind11/pybind11.h> #include <pybind11/stl.h> #include <typeindex> #include <unordered_map> #include <any> #include <stdexcept> class Map { private: std::unordered_map<std::string, std::any> data_store; std::unordered_map<std::string, std::type_index> type_records; public: // 模板set方法,存储值并记录类型 template <typename T> void set(const std::string& key, T value) { data_store[key] = std::move(value); type_records[key] = std::type_index(typeid(T)); } // 模板get方法,供C++内部调用 template <typename T> T get(const std::string& key) const { return std::any_cast<T>(data_store.at(key)); } // 非模板方法,供pybind11暴露给Python pybind11::object get_py(const std::string& key) const { auto type_iter = type_records.find(key); if (type_iter == type_records.end()) { throw std::out_of_range("Key not exists"); } const auto& entry_type = type_iter->second; if (entry_type == std::type_index(typeid(int))) { return pybind11::cast(get<int>(key)); } else if (entry_type == std::type_index(typeid(std::string))) { return pybind11::cast(get<std::string>(key)); } // 可扩展添加更多支持的数据类型分支 throw std::runtime_error("Unsupported data type for this key"); } };用pybind11绑定适配方法并命名为get
在pybind11的模块绑定代码中,将get_py方法暴露为Python端的get方法,这样Python代码就可以直接调用map.get(key):PYBIND11_MODULE(my_map_module, m) { pybind11::class_<Map>(m, "Map") .def(pybind11::init<>()) .def("set", &Map::set<int>) .def("set", &Map::set<std::string>) // 按需绑定更多类型的set方法 .def("get", &Map::get_py); }
效果验证
编译生成模块后,在Python中即可直接使用:
from my_map_module import Map map = Map() map.set("my-int-entry", 42) map.set("my-string-entry", "hello") a = map.get("my-int-entry") print(type(a)) # 输出 <class 'int'> b = map.get("my-string-entry") print(type(b)) # 输出 <class 'str'>
如果你的Map类内部有自己的类型存储机制(而非示例中的std::any),只需调整get_py方法中的类型判断逻辑即可,核心是在C++层完成运行时类型的分发工作。
内容的提问来源于stack exchange,提问作者user3207838

