Make std::map pickable #5266
-
| Hello, but I am having trouble with the  Is it possible to make this std::map pickable? If so, Can anyone point me in the right direction? Any help would be much appreciated. | 
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
| Hi @alvaro-diaz-valenzuela you need to manually cast to desired pair in a loop. Automatic iteration over py::tuple is simply catching py::handle instances -- thus the translation to std::pair is simply unknown, you probably would need  Here is what you need to do (I have simplified the  class QCDate;  // forward declarations
PYBIND11_MAKE_OPAQUE(std::map<QCDate, double>);
namespace py = pybind11;
class QCDate {
public:
	QCDate() {}
	QCDate(int v) : value{v} {}
    bool operator<(const QCDate& other) const
    {
        return value < other.value;
    }
	int value;
};
PYBIND11_MODULE(mymodule, m)
{
    m.doc() = "This is the module test";
    py::class_<QCDate>(m, "QCDate")
        .def(py::init<>())
        .def(py::init<int>())
        .def(py::pickle(
                    [](const QCDate &self) { // __getstate__
                        return py::make_tuple(self.value);
                    },
                    [](py::tuple t) { // __setstate__
                        if (t.size() != 1)
                            throw std::runtime_error("Invalid state!");
                        QCDate obj(t[0].cast<int>());
                        return obj;
                    }
            ))
        .def("__repr__", [](QCDate &self){
            return std::string("QCDate:") + std::to_string(self.value);
        });
    py::bind_map<std::map<QCDate, double>>(m, "time_series")
        .def(py::pickle(
                    [](std::map<QCDate, double> &self) { // __getstate__
                        std::vector<py::tuple> items;
                        for (auto& element : self) {
                            items.emplace_back(py::cast(element));
                        }
                        return py::make_tuple(items);
                    },
                    [](py::tuple t) { // __setstate__
                        if (t.size() != 1)
                            throw std::runtime_error("Invalid state!");
                        std::map<QCDate, double> result;
                        for(auto tuple_item : t[0]) {
                            auto p = tuple_item.cast<std::pair<QCDate, double>>();
                            result[p.first] = p.second;
                        }
                        return result;
                    }
            ));
}Here is code snippet for Python: import mymodule
import pickle
ts = mymodule.time_series()
ts[mymodule.QCDate(21)] = 4.2
ts[mymodule.QCDate(37)] = 2.5
ts[mymodule.QCDate(42)] = 9.6
p = pickle.dumps(ts)
loaded = pickle.loads(p)
print(dict(loaded))  # that will print: {QCDate:21: 4.2, QCDate:37: 2.5, QCDate:42: 9.6}Hope that helps, cheers! PS I have edited the answer as I missed the custom map in testing:) | 
Beta Was this translation helpful? Give feedback.
Hi @alvaro-diaz-valenzuela you need to manually cast to desired pair in a loop. Automatic iteration over py::tuple is simply catching py::handle instances -- thus the translation to std::pair is simply unknown, you probably would need
type_casterfor it. However, there is a simpler way, written below.Here is what you need to do (I have simplified the
QCData):