Skip to content

Commit 5606283

Browse files
committed
pickling support (fixes #144)
1 parent 505466f commit 5606283

File tree

8 files changed

+157
-2
lines changed

8 files changed

+157
-2
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ set(PYBIND11_EXAMPLES
120120
example/example12.cpp
121121
example/example13.cpp
122122
example/example14.cpp
123+
example/example15.cpp
123124
example/issues.cpp
124125
)
125126

docs/advanced.rst

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ objects (e.g. a NumPy matrix).
841841
The file :file:`example/example7.cpp` contains a complete example that
842842
demonstrates using the buffer protocol with pybind11 in more detail.
843843

844-
.. [#f1] https://docs.python.org/3/c-api/buffer.html
844+
.. [#f1] http://docs.python.org/3/c-api/buffer.html
845845
846846
NumPy support
847847
=============
@@ -1184,3 +1184,70 @@ set of admissible operations.
11841184

11851185
The file :file:`example/example14.cpp` contains a complete example that
11861186
demonstrates how to create opaque types using pybind11 in more detail.
1187+
1188+
Pickling support
1189+
================
1190+
1191+
Python's ``pickle`` module provides a powerful facility to serialize and
1192+
de-serialize a Python object graph into a binary data stream. To pickle and
1193+
unpickle C++ classes using pybind11, two additional functions most be provided.
1194+
Suppose the class in question has the following signature:
1195+
1196+
.. code-block:: cpp
1197+
1198+
class Pickleable {
1199+
public:
1200+
Pickleable(const std::string &value) : m_value(value) { }
1201+
const std::string &value() const { return m_value; }
1202+
1203+
void setExtra(int extra) { m_extra = extra; }
1204+
int extra() const { return m_extra; }
1205+
private:
1206+
std::string m_value;
1207+
int m_extra = 0;
1208+
};
1209+
1210+
The binding code including the requisite ``__setstate__`` and ``__getstate__`` methods [#f2]_
1211+
looks as follows:
1212+
1213+
.. code-block:: cpp
1214+
1215+
py::class_<Pickleable>(m, "Pickleable")
1216+
.def(py::init<std::string>())
1217+
.def("value", &Pickleable::value)
1218+
.def("extra", &Pickleable::extra)
1219+
.def("setExtra", &Pickleable::setExtra)
1220+
.def("__getstate__", [](const Pickleable &p) {
1221+
/* Return a tuple that fully encodes the state of the object */
1222+
return py::make_tuple(p.value(), p.extra());
1223+
})
1224+
.def("__setstate__", [](Pickleable &p, py::tuple t) {
1225+
if (t.size() != 2)
1226+
throw std::runtime_error("Invalid state!");
1227+
1228+
/* Invoke the constructor (need to use in-place version) */
1229+
new (&p) Pickleable(t[0].cast<std::string>());
1230+
1231+
/* Assign any additional state */
1232+
p.setExtra(t[1].cast<int>());
1233+
});
1234+
1235+
An instance can now be pickled as follows:
1236+
1237+
.. code-block:: python
1238+
1239+
import cPickle # important
1240+
p = Pickleable("test_value")
1241+
p.setExtra(15)
1242+
data = cPickle.dumps(p, -1)
1243+
1244+
Note that only the cPickle module is supported for now. It is important to
1245+
request usage of the highest protocol version using the ``-1`` argument to
1246+
``dumps``.
1247+
1248+
.. seealso::
1249+
1250+
The file :file:`example/example15.cpp` contains a complete example that
1251+
demonstrates how to pickle and unpickle types using pybind11 in more detail.
1252+
1253+
.. [#f2] http://docs.python.org/3/library/pickle.html#pickling-class-instances

docs/changelog.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ Changelog
55

66
1.5 (not yet released)
77
----------------------
8+
* Pickling support
9+
* Added a variadic ``make_tuple()`` function
810
* Address a rare issue that could confuse the current virtual function dispatcher
9-
* Documentation improvements: import issues, symbol visibility, limitations
11+
* Documentation improvements: import issues, symbol visibility, pickling, limitations
1012

1113
1.4 (April 7, 2016)
1214
--------------------------

example/example.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ void init_ex11(py::module &);
2323
void init_ex12(py::module &);
2424
void init_ex13(py::module &);
2525
void init_ex14(py::module &);
26+
void init_ex15(py::module &);
2627
void init_issues(py::module &);
2728

2829
PYBIND11_PLUGIN(example) {
@@ -42,6 +43,7 @@ PYBIND11_PLUGIN(example) {
4243
init_ex12(m);
4344
init_ex13(m);
4445
init_ex14(m);
46+
init_ex15(m);
4547
init_issues(m);
4648

4749
return m.ptr();

example/example15.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
example/example15.cpp -- pickle support
3+
4+
Copyright (c) 2015 Wenzel Jakob <[email protected]>
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
10+
#include "example.h"
11+
12+
class Pickleable {
13+
public:
14+
Pickleable(const std::string &value) : m_value(value) { }
15+
const std::string &value() const { return m_value; }
16+
17+
void setExtra1(int extra1) { m_extra1 = extra1; }
18+
void setExtra2(int extra2) { m_extra2 = extra2; }
19+
int extra1() const { return m_extra1; }
20+
int extra2() const { return m_extra2; }
21+
private:
22+
std::string m_value;
23+
int m_extra1 = 0;
24+
int m_extra2 = 0;
25+
};
26+
27+
void init_ex15(py::module &m) {
28+
py::class_<Pickleable>(m, "Pickleable")
29+
.def(py::init<std::string>())
30+
.def("value", &Pickleable::value)
31+
.def("extra1", &Pickleable::extra1)
32+
.def("extra2", &Pickleable::extra2)
33+
.def("setExtra1", &Pickleable::setExtra1)
34+
.def("setExtra2", &Pickleable::setExtra2)
35+
// For details on the methods below, refer to
36+
// http://docs.python.org/3/library/pickle.html#pickling-class-instances
37+
.def("__getstate__", [](const Pickleable &p) {
38+
/* Return a tuple that fully encodes the state of the object */
39+
return py::make_tuple(p.value(), p.extra1(), p.extra2());
40+
})
41+
.def("__setstate__", [](Pickleable &p, py::tuple t) {
42+
if (t.size() != 3)
43+
throw std::runtime_error("Invalid state!");
44+
/* Invoke the constructor (need to use in-place version) */
45+
new (&p) Pickleable(t[0].cast<std::string>());
46+
47+
/* Assign any additional state */
48+
p.setExtra1(t[1].cast<int>());
49+
p.setExtra2(t[2].cast<int>());
50+
});
51+
}

example/example15.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import print_function
2+
import sys
3+
4+
sys.path.append('.')
5+
6+
from example import Pickleable
7+
8+
import cPickle as pickle # Only cPickle is supported for now
9+
10+
p = Pickleable("test_value")
11+
p.setExtra1(15)
12+
p.setExtra2(48)
13+
14+
data = pickle.dumps(p, -1) # -1 is important (use highest protocol version)
15+
print("%s %i %i" % (p.value(), p.extra1(), p.extra2()))
16+
17+
p2 = pickle.loads(data)
18+
print("%s %i %i" % (p2.value(), p2.extra1(), p2.extra2()))

example/example15.ref

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
test_value 15 48
2+
test_value 15 48

include/pybind11/pytypes.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ class accessor {
142142
return result;
143143
}
144144

145+
template <typename T> inline T cast() const { return operator object().cast<T>(); }
146+
145147
operator bool() const {
146148
if (attr) {
147149
return (bool) PyObject_HasAttr(obj.ptr(), key.ptr());
@@ -161,18 +163,23 @@ class accessor {
161163
struct list_accessor {
162164
public:
163165
list_accessor(handle list, size_t index) : list(list), index(index) { }
166+
164167
void operator=(list_accessor o) { return operator=(object(o)); }
168+
165169
void operator=(const handle &o) {
166170
// PyList_SetItem steals a reference to 'o'
167171
if (PyList_SetItem(list.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0)
168172
pybind11_fail("Unable to assign value in Python list!");
169173
}
174+
170175
operator object() const {
171176
PyObject *result = PyList_GetItem(list.ptr(), (ssize_t) index);
172177
if (!result)
173178
pybind11_fail("Unable to retrieve value from Python list!");
174179
return object(result, true);
175180
}
181+
182+
template <typename T> inline T cast() const { return operator object().cast<T>(); }
176183
private:
177184
handle list;
178185
size_t index;
@@ -181,18 +188,23 @@ struct list_accessor {
181188
struct tuple_accessor {
182189
public:
183190
tuple_accessor(handle tuple, size_t index) : tuple(tuple), index(index) { }
191+
184192
void operator=(tuple_accessor o) { return operator=(object(o)); }
193+
185194
void operator=(const handle &o) {
186195
// PyTuple_SetItem steals a referenceto 'o'
187196
if (PyTuple_SetItem(tuple.ptr(), (ssize_t) index, o.inc_ref().ptr()) < 0)
188197
pybind11_fail("Unable to assign value in Python tuple!");
189198
}
199+
190200
operator object() const {
191201
PyObject *result = PyTuple_GetItem(tuple.ptr(), (ssize_t) index);
192202
if (!result)
193203
pybind11_fail("Unable to retrieve value from Python tuple!");
194204
return object(result, true);
195205
}
206+
207+
template <typename T> inline T cast() const { return operator object().cast<T>(); }
196208
private:
197209
handle tuple;
198210
size_t index;

0 commit comments

Comments
 (0)