Skip to content

Making a C++ function pickleable? #1261

Description

@r-owen

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions