-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Issue description
Is there a practical way to make a C++ function pickleable? I want to do this in order to support pickle on my own classes with __reduce__.
In more detail, I am wrapping a hierarchy of C++ objects. I have a C++ factory function that will return a shared_ptr to a suitable object in the hierarchy given its serialization (the internal details don't matter to this question):
std::shared_ptr<Object> makeObject(std::string const &state);
As such it is trivial to use __reduce__ to add pickling support, and I think __getstate__ and __setstate__ would be a lot messier. I would like to add this Object.__reduce__:
cls.def("__reduce__", [](Object const &self) {
auto unpickleArgs = py::make_tuple( self.serialize(false));
return py::make_tuple(py::cpp_function(makeObject), unpickleArgs);
});
This compiles, and Object.__reduce__() runs correctly, but when I try to pickle objects I get this error: TypeError: can't pickle PyCapsule objects
I have worked around the problem by creating a functor class that does the same thing as makeObject and adding a __reduce__ method to that functor so it can be pickled:
class ObjectMaker {
public:
ObjectMaker() = default;
std::shared_ptr<Object> operator()(std::string const &state);
};
...
py::class_<ObjectMaker, std::shared_ptr<ObjectMaker>> makerCls(mod, "ObjectMaker");
makerCls.def(py::init<>());
makerCls.def("__call__", &ObjectMaker::operator());
makerCls.def("__reduce__", [makerCls](ObjectMaker const &self) {
return py::make_tuple(makerCls, py::tuple());
});
However, if somebody knows a simple way to add support to my makeObject function I could avoid the functor and its messy wrapper.
I have included a simple example below. There is no class hierarchy and so no need for __reduce__, but it shows the issue.
Reproducible example code
C++ Code
#include <pybind11/pybind11.h>
namespace py = pybind11;
class Temp {
public:
std::string id() { return "a Temp"; }
};
Temp makeTemp() { return Temp(); }
PYBIND11_PLUGIN(temp) {
py::module mod("temp");
py::class_<Temp, std::shared_ptr<Temp>> cls(mod, "Temp");
cls.def(py::init<>());
cls.def("id", &Temp::id);
cls.def("__reduce__", [](Temp const &self) {
return py::make_tuple(py::cpp_function(makeTemp), py::make_tuple());
});
return mod.ptr();
}
Python code
import pickle
from example import temp
t = Temp()
print(t.id())
print(t.__reduce__())
print(pickle.dumps(t))
Results
This prints:
>>> example.Test().__reduce__()
(<built-in method of PyCapsule object at 0x104381b10>, ())
>>> pickle.dumps(example.Test())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't pickle PyCapsule objects