Skip to content

Commit 4ef5f77

Browse files
author
Ralf W. Grosse-Kunstleve
committed
additional files for pickle support; no modification of any existing files
[SVN r14549]
1 parent d27e5a5 commit 4ef5f77

File tree

8 files changed

+539
-0
lines changed

8 files changed

+539
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// (C) Copyright R.W. Grosse-Kunstleve 2002.
2+
// Permission to copy, use, modify, sell and distribute this software
3+
// is granted provided this copyright notice appears in all copies. This
4+
// software is provided "as is" without express or implied warranty, and
5+
// with no claim as to its suitability for any purpose.
6+
#ifndef BOOST_PYTHON_OBJECT_PICKLE_SUPPORT_RWGK20020603_HPP
7+
#define BOOST_PYTHON_OBJECT_PICKLE_SUPPORT_RWGK20020603_HPP
8+
9+
#include <boost/python/object/class.hpp>
10+
#include <boost/python/tuple.hpp>
11+
12+
namespace boost { namespace python {
13+
14+
handle<> make_instance_reduce_function();
15+
16+
namespace error_messages {
17+
18+
template <class T>
19+
struct missing_pickle_support_function_or_incorrect_signature {};
20+
21+
}
22+
23+
class pickle_support_base
24+
{
25+
private:
26+
struct dummy_return_type_ {};
27+
28+
public:
29+
template <class Class_, class Tgetinitargs>
30+
static
31+
void
32+
register_(
33+
Class_& cl,
34+
tuple (*getinitargs_fn)(Tgetinitargs),
35+
dummy_return_type_* (*getstate_fn)(),
36+
dummy_return_type_* (*setstate_fn)(),
37+
bool)
38+
{
39+
cl.enable_pickle_support(false);
40+
cl.def("__getinitargs__", getinitargs_fn);
41+
}
42+
43+
template <class Class_, class Tgetstate, class Tsetstate>
44+
static
45+
void
46+
register_(
47+
Class_& cl,
48+
dummy_return_type_* (*getinitargs_fn)(),
49+
tuple (*getstate_fn)(Tgetstate),
50+
void (*setstate_fn)(Tsetstate, object),
51+
bool getstate_manages_dict)
52+
{
53+
cl.enable_pickle_support(getstate_manages_dict);
54+
cl.def("__getstate__", getstate_fn);
55+
cl.def("__setstate__", setstate_fn);
56+
}
57+
58+
template <class Class_,
59+
class Tgetinitargs, class Tgetstate, class Tsetstate>
60+
static
61+
void
62+
register_(
63+
Class_& cl,
64+
tuple (*getinitargs_fn)(Tgetinitargs),
65+
tuple (*getstate_fn)(Tgetstate),
66+
void (*setstate_fn)(Tsetstate, object),
67+
bool getstate_manages_dict)
68+
{
69+
cl.enable_pickle_support(getstate_manages_dict);
70+
cl.def("__getinitargs__", getinitargs_fn);
71+
cl.def("__getstate__", getstate_fn);
72+
cl.def("__setstate__", setstate_fn);
73+
}
74+
75+
template <class Class_>
76+
static
77+
void
78+
register_(
79+
Class_&,
80+
...)
81+
{
82+
typedef typename
83+
error_messages::missing_pickle_support_function_or_incorrect_signature<
84+
Class_>::error_type error_type;
85+
}
86+
87+
static dummy_return_type_* getinitargs() { return 0; }
88+
static dummy_return_type_* getstate() { return 0; }
89+
static dummy_return_type_* setstate() { return 0; }
90+
91+
static bool getstate_manages_dict() { return false; }
92+
};
93+
94+
}} // namespace boost::python
95+
96+
#endif // BOOST_PYTHON_OBJECT_PICKLE_SUPPORT_RWGK20020603_HPP

src/object/pickle_support.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// (C) Copyright R.W. Grosse-Kunstleve 2002.
2+
// Permission to copy, use, modify, sell and distribute this software
3+
// is granted provided this copyright notice appears in all copies. This
4+
// software is provided "as is" without express or implied warranty, and
5+
// with no claim as to its suitability for any purpose.
6+
7+
#include <boost/python/make_function.hpp>
8+
#include <boost/python/object/class.hpp>
9+
#include <boost/python/tuple.hpp>
10+
#include <boost/python/list.hpp>
11+
#include <boost/python/dict.hpp>
12+
#include <boost/python/detail/api_placeholder.hpp>
13+
14+
namespace boost { namespace python {
15+
16+
namespace {
17+
18+
tuple instance_reduce(object instance_obj)
19+
{
20+
list result;
21+
object instance_class(instance_obj.attr("__class__"));
22+
result.append(instance_class);
23+
object none;
24+
object getinitargs = getattr(instance_obj, "__getinitargs__", none);
25+
tuple initargs;
26+
if (getinitargs.ptr() != none.ptr()) {
27+
initargs = tuple(getinitargs());
28+
}
29+
result.append(initargs);
30+
object getstate = getattr(instance_obj, "__getstate__", none);
31+
object instance_dict = getattr(instance_obj, "__dict__", none);
32+
long len_instance_dict = 0;
33+
if (instance_dict.ptr() != none.ptr()) {
34+
len_instance_dict = len(instance_dict);
35+
}
36+
if (getstate.ptr() != none.ptr()) {
37+
if (len_instance_dict > 0) {
38+
object getstate_manages_dict = getattr(
39+
instance_obj, "__getstate_manages_dict__", none);
40+
if (getstate_manages_dict.ptr() == none.ptr()) {
41+
PyErr_SetString(PyExc_RuntimeError,
42+
"Incomplete pickle support"
43+
" (__getstate_manages_dict__ not set)");
44+
throw_error_already_set();
45+
}
46+
}
47+
result.append(getstate());
48+
}
49+
else if (len_instance_dict > 0) {
50+
result.append(instance_dict);
51+
}
52+
return tuple(result);
53+
}
54+
55+
} // namespace
56+
57+
handle<> make_instance_reduce_function()
58+
{
59+
static handle<> result(make_function(&instance_reduce));
60+
return result;
61+
}
62+
63+
}} // namespace boost::python

test/pickle1.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Example by Ralf W. Grosse-Kunstleve
2+
3+
/*
4+
This example shows how to make an Extension Class "pickleable".
5+
6+
The world class below can be fully restored by passing the
7+
appropriate argument to the constructor. Therefore it is sufficient
8+
to define the pickle interface method __getinitargs__.
9+
10+
For more information refer to boost/libs/python/doc/pickle.html.
11+
*/
12+
13+
#include <boost/python/module.hpp>
14+
#include <boost/python/class.hpp>
15+
#include <boost/python/list.hpp>
16+
#include <boost/python/tuple.hpp>
17+
18+
#include <string>
19+
20+
namespace {
21+
22+
// A friendly class.
23+
class world
24+
{
25+
private:
26+
std::string country;
27+
public:
28+
world(const std::string& country) {
29+
this->country = country;
30+
}
31+
std::string greet() const { return "Hello from " + country + "!"; }
32+
std::string get_country() const { return country; }
33+
};
34+
35+
struct world_pickle_support : boost::python::pickle_support_base
36+
{
37+
static
38+
boost::python::tuple
39+
getinitargs(const world& w)
40+
{
41+
using namespace boost::python;
42+
list result;
43+
result.append(object(w.get_country()));
44+
return tuple(result);
45+
}
46+
};
47+
48+
}
49+
50+
BOOST_PYTHON_MODULE_INIT(pickle1_ext)
51+
{
52+
using namespace boost::python;
53+
module("pickle1_ext")
54+
.add(class_<world>("world")
55+
.def_init(args<const std::string&>())
56+
.def("greet", &world::greet)
57+
.pickle_support(world_pickle_support())
58+
)
59+
;
60+
}

test/pickle1.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
r'''>>> import pickle1_ext
2+
>>> import pickle
3+
>>> pickle1_ext.world.__module__
4+
'pickle1_ext'
5+
>>> pickle1_ext.world.__safe_for_unpickling__
6+
1
7+
>>> pickle1_ext.world.__name__
8+
'world'
9+
>>> pickle1_ext.world('Hello').__reduce__()
10+
(<class 'pickle1_ext.world'>, ('Hello',))
11+
>>> wd = pickle1_ext.world('California')
12+
>>> pstr = pickle.dumps(wd)
13+
>>> wl = pickle.loads(pstr)
14+
>>> print wd.greet()
15+
Hello from California!
16+
>>> print wl.greet()
17+
Hello from California!
18+
'''
19+
20+
def run(args = None):
21+
if args is not None:
22+
import sys
23+
sys.argv = args
24+
import doctest, pickle1
25+
return doctest.testmod(pickle1)
26+
27+
if __name__ == '__main__':
28+
import sys
29+
sys.exit(run()[0])
30+

test/pickle2.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Example by Ralf W. Grosse-Kunstleve
2+
3+
/*
4+
This example shows how to make an Extension Class "pickleable".
5+
6+
The world class below contains member data (secret_number) that
7+
cannot be restored by any of the constructors. Therefore it is
8+
necessary to provide the __getstate__/__setstate__ pair of pickle
9+
interface methods.
10+
11+
For simplicity, the __dict__ is not included in the result of
12+
__getstate__. This is not generally recommended, but a valid
13+
approach if it is anticipated that the object's __dict__ will
14+
always be empty. Note that safety guards are provided to catch
15+
the cases where this assumption is not true.
16+
17+
pickle3.cpp shows how to include the object's __dict__ in the
18+
result of __getstate__.
19+
20+
For more information refer to boost/libs/python/doc/pickle.html.
21+
*/
22+
23+
#include <string>
24+
25+
#include <boost/python/module.hpp>
26+
#include <boost/python/class.hpp>
27+
#include <boost/python/list.hpp>
28+
#include <boost/python/tuple.hpp>
29+
#include <boost/python/extract.hpp>
30+
#include <boost/python/detail/api_placeholder.hpp>
31+
32+
namespace { // Avoid cluttering the global namespace.
33+
34+
// A friendly class.
35+
class world
36+
{
37+
public:
38+
world(const std::string& country) : secret_number(0) {
39+
this->country = country;
40+
}
41+
std::string greet() const { return "Hello from " + country + "!"; }
42+
std::string get_country() const { return country; }
43+
void set_secret_number(int number) { secret_number = number; }
44+
int get_secret_number() const { return secret_number; }
45+
private:
46+
std::string country;
47+
int secret_number;
48+
};
49+
50+
struct world_pickle_support : boost::python::pickle_support_base
51+
{
52+
static
53+
boost::python::tuple
54+
getinitargs(const world& w)
55+
{
56+
using namespace boost::python;
57+
list result;
58+
result.append(object(w.get_country()));
59+
return tuple(result);
60+
}
61+
62+
static
63+
boost::python::tuple
64+
getstate(const world& w)
65+
{
66+
using namespace boost::python;
67+
list result;
68+
result.append(object(w.get_secret_number()));
69+
return tuple(result);
70+
}
71+
72+
static
73+
void
74+
setstate(world& w, boost::python::object state)
75+
{
76+
using namespace boost::python;
77+
extract<tuple> state_proxy(state);
78+
if (!state_proxy.check() || len(state_proxy()) != 1) {
79+
PyErr_SetString(PyExc_ValueError,
80+
"Unexpected argument in call to __setstate__.");
81+
throw_error_already_set();
82+
}
83+
long number = extract<long>(state_proxy()[0])();
84+
if (number != 42) w.set_secret_number(number);
85+
}
86+
};
87+
88+
}
89+
90+
BOOST_PYTHON_MODULE_INIT(pickle2_ext)
91+
{
92+
boost::python::module("pickle2_ext")
93+
.add(boost::python::class_<world>("world")
94+
.def_init(boost::python::args<const std::string&>())
95+
.def("greet", &world::greet)
96+
.def("get_secret_number", &world::get_secret_number)
97+
.def("set_secret_number", &world::set_secret_number)
98+
.pickle_support(world_pickle_support())
99+
)
100+
;
101+
}

test/pickle2.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
r'''>>> import pickle2_ext
2+
>>> import pickle
3+
>>> pickle2_ext.world.__module__
4+
'pickle2_ext'
5+
>>> pickle2_ext.world.__safe_for_unpickling__
6+
1
7+
>>> pickle2_ext.world.__name__
8+
'world'
9+
>>> pickle2_ext.world('Hello').__reduce__()
10+
(<class 'pickle2_ext.world'>, ('Hello',), (0,))
11+
>>> for number in (24, 42):
12+
... wd = pickle2_ext.world('California')
13+
... wd.set_secret_number(number)
14+
... pstr = pickle.dumps(wd)
15+
... wl = pickle.loads(pstr)
16+
... print wd.greet(), wd.get_secret_number()
17+
... print wl.greet(), wl.get_secret_number()
18+
Hello from California! 24
19+
Hello from California! 24
20+
Hello from California! 42
21+
Hello from California! 0
22+
23+
# Now show that the __dict__ is not taken care of.
24+
>>> wd = pickle2_ext.world('California')
25+
>>> wd.x = 1
26+
>>> wd.__dict__
27+
{'x': 1}
28+
>>> try: pstr = pickle.dumps(wd)
29+
... except RuntimeError, err: print err[0]
30+
...
31+
Incomplete pickle support (__getstate_manages_dict__ not set)
32+
'''
33+
34+
def run(args = None):
35+
if args is not None:
36+
import sys
37+
sys.argv = args
38+
import doctest, pickle2
39+
return doctest.testmod(pickle2)
40+
41+
if __name__ == '__main__':
42+
import sys
43+
sys.exit(run()[0])
44+

0 commit comments

Comments
 (0)