diff --git a/CMakeLists.txt b/CMakeLists.txt index 900e26ec..6b5ccf3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ set(PYBIND11_EXAMPLES example/example12.cpp example/example13.cpp example/example14.cpp + example/example15.cpp example/issues.cpp ) diff --git a/docs/advanced.rst b/docs/advanced.rst index 86ab34d2..bf0c6eb2 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -841,7 +841,7 @@ objects (e.g. a NumPy matrix). The file :file:`example/example7.cpp` contains a complete example that demonstrates using the buffer protocol with pybind11 in more detail. -.. [#f1] https://docs.python.org/3/c-api/buffer.html +.. [#f1] http://docs.python.org/3/c-api/buffer.html NumPy support ============= @@ -1184,3 +1184,74 @@ set of admissible operations. The file :file:`example/example14.cpp` contains a complete example that demonstrates how to create opaque types using pybind11 in more detail. + +Pickling support +================ + +Python's ``pickle`` module provides a powerful facility to serialize and +de-serialize a Python object graph into a binary data stream. To pickle and +unpickle C++ classes using pybind11, two additional functions most be provided. +Suppose the class in question has the following signature: + +.. code-block:: cpp + + class Pickleable { + public: + Pickleable(const std::string &value) : m_value(value) { } + const std::string &value() const { return m_value; } + + void setExtra(int extra) { m_extra = extra; } + int extra() const { return m_extra; } + private: + std::string m_value; + int m_extra = 0; + }; + +The binding code including the requisite ``__setstate__`` and ``__getstate__`` methods [#f2]_ +looks as follows: + +.. code-block:: cpp + + py::class_(m, "Pickleable") + .def(py::init()) + .def("value", &Pickleable::value) + .def("extra", &Pickleable::extra) + .def("setExtra", &Pickleable::setExtra) + .def("__getstate__", [](const Pickleable &p) { + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p.value(), p.extra()); + }) + .def("__setstate__", [](Pickleable &p, py::tuple t) { + if (t.size() != 2) + throw std::runtime_error("Invalid state!"); + + /* Invoke the constructor (need to use in-place version) */ + new (&p) Pickleable(t[0].cast()); + + /* Assign any additional state */ + p.setExtra(t[1].cast()); + }); + +An instance can now be pickled as follows: + +.. code-block:: python + + try: + import cPickle as pickle # Use cPickle on Python 2.7 + except ImportError: + import pickle + + p = Pickleable("test_value") + p.setExtra(15) + data = cPickle.dumps(p, -1) + +Note that only the cPickle module is supported on Python 2.7. It is also +important to request usage of the highest protocol version using the ``-1`` +argument to ``dumps``. + +.. seealso:: + + The file :file:`example/example15.cpp` contains a complete example that + demonstrates how to pickle and unpickle types using pybind11 in more detail. + +.. [#f2] http://docs.python.org/3/library/pickle.html#pickling-class-instances diff --git a/docs/changelog.rst b/docs/changelog.rst index 28210f9d..f8f717bf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,8 +5,10 @@ Changelog 1.5 (not yet released) ---------------------- +* Pickling support +* Added a variadic ``make_tuple()`` function * Address a rare issue that could confuse the current virtual function dispatcher -* Documentation improvements: import issues, symbol visibility, limitations +* Documentation improvements: import issues, symbol visibility, pickling, limitations 1.4 (April 7, 2016) -------------------------- diff --git a/example/example.cpp b/example/example.cpp index a4fb4d3f..bc0ee417 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -23,6 +23,7 @@ void init_ex11(py::module &); void init_ex12(py::module &); void init_ex13(py::module &); void init_ex14(py::module &); +void init_ex15(py::module &); void init_issues(py::module &); PYBIND11_PLUGIN(example) { @@ -42,6 +43,7 @@ PYBIND11_PLUGIN(example) { init_ex12(m); init_ex13(m); init_ex14(m); + init_ex15(m); init_issues(m); return m.ptr(); diff --git a/example/example15.cpp b/example/example15.cpp new file mode 100644 index 00000000..bfc75268 --- /dev/null +++ b/example/example15.cpp @@ -0,0 +1,51 @@ +/* + example/example15.cpp -- pickle support + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "example.h" + +class Pickleable { +public: + Pickleable(const std::string &value) : m_value(value) { } + const std::string &value() const { return m_value; } + + void setExtra1(int extra1) { m_extra1 = extra1; } + void setExtra2(int extra2) { m_extra2 = extra2; } + int extra1() const { return m_extra1; } + int extra2() const { return m_extra2; } +private: + std::string m_value; + int m_extra1 = 0; + int m_extra2 = 0; +}; + +void init_ex15(py::module &m) { + py::class_(m, "Pickleable") + .def(py::init()) + .def("value", &Pickleable::value) + .def("extra1", &Pickleable::extra1) + .def("extra2", &Pickleable::extra2) + .def("setExtra1", &Pickleable::setExtra1) + .def("setExtra2", &Pickleable::setExtra2) + // For details on the methods below, refer to + // http://docs.python.org/3/library/pickle.html#pickling-class-instances + .def("__getstate__", [](const Pickleable &p) { + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p.value(), p.extra1(), p.extra2()); + }) + .def("__setstate__", [](Pickleable &p, py::tuple t) { + if (t.size() != 3) + throw std::runtime_error("Invalid state!"); + /* Invoke the constructor (need to use in-place version) */ + new (&p) Pickleable(t[0].cast()); + + /* Assign any additional state */ + p.setExtra1(t[1].cast()); + p.setExtra2(t[2].cast()); + }); +} diff --git a/example/example15.py b/example/example15.py new file mode 100644 index 00000000..9868b62c --- /dev/null +++ b/example/example15.py @@ -0,0 +1,21 @@ +from __future__ import print_function +import sys + +sys.path.append('.') + +from example import Pickleable + +try: + import cPickle as pickle # Use cPickle on Python 2.7 +except ImportError: + import pickle + +p = Pickleable("test_value") +p.setExtra1(15) +p.setExtra2(48) + +data = pickle.dumps(p, -1) # -1 is important (use highest protocol version) +print("%s %i %i" % (p.value(), p.extra1(), p.extra2())) + +p2 = pickle.loads(data) +print("%s %i %i" % (p2.value(), p2.extra1(), p2.extra2())) diff --git a/example/example15.ref b/example/example15.ref new file mode 100644 index 00000000..d804973a --- /dev/null +++ b/example/example15.ref @@ -0,0 +1,2 @@ +test_value 15 48 +test_value 15 48 diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 8e5cdc6e..455d8931 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -142,6 +142,8 @@ public: return result; } + template inline T cast() const { return operator object().cast(); } + operator bool() const { if (attr) { return (bool) PyObject_HasAttr(obj.ptr(), key.ptr()); @@ -161,18 +163,23 @@ private: struct list_accessor { public: list_accessor(handle list, size_t index) : list(list), index(index) { } + void operator=(list_accessor o) { return operator=(object(o)); } + void operator=(const handle &o) { // PyList_SetItem steals a reference to 'o' if (PyList_SetItem(list.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0) pybind11_fail("Unable to assign value in Python list!"); } + operator object() const { PyObject *result = PyList_GetItem(list.ptr(), (ssize_t) index); if (!result) pybind11_fail("Unable to retrieve value from Python list!"); return object(result, true); } + + template inline T cast() const { return operator object().cast(); } private: handle list; size_t index; @@ -181,18 +188,23 @@ private: struct tuple_accessor { public: tuple_accessor(handle tuple, size_t index) : tuple(tuple), index(index) { } + void operator=(tuple_accessor o) { return operator=(object(o)); } + void operator=(const handle &o) { // PyTuple_SetItem steals a referenceto 'o' if (PyTuple_SetItem(tuple.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0) pybind11_fail("Unable to assign value in Python tuple!"); } + operator object() const { PyObject *result = PyTuple_GetItem(tuple.ptr(), (ssize_t) index); if (!result) pybind11_fail("Unable to retrieve value from Python tuple!"); return object(result, true); } + + template inline T cast() const { return operator object().cast(); } private: handle tuple; size_t index;