I have a use case involving Boost Serialization and Boost Python. I have
a (mostly) C++ library wrapped with Python. The C++ code uses Boost
Serialization to periodically create checkpoints from which the user can
resume later. Getting the serialization and python libraries to work
together didn't present any special problems until I had a case in which
the user had a derived Python class (from a C++ base). In searching the
web, I have seen people asking about similar cases, but never an answer.
See, e.g.,
http://stackoverflow.com/questions/7289897/boost-serialization-and-boost-python-two-way-pickle
<http://stackoverflow.com/questions/7289897/boost-serialization-and-boost-python-two-way-pickle>
.
I have a partial solution to the problem using Boost Serialization and
Python Pickle together. I would appreciate any advice on how to improve
it. My solution is limited in that the state of the base C++ object is
not restored. I also get an extra copy of the C++ object. Since my
current use case has no state in the base object, I can live with these
limitations. I would however, like to find a full solution.
In the files I have attached, I can successfully run three variations on
a simple test case. A class of type Base is passed to a class of type
Caller. Caller calls the passed class's doit method twice, saves
(checkpoints) its state, then calls the doit method again. The resume
script restores the state of the Caller and Base classes, then calls the
doit method again. If successful, the resumed doit call should be the
same as the third doit call from the original script.
In testcheckpoint1.py, the Base class is the original (C++) Base. It has
no state, so the example is pretty trivial. Its doit method prints
"Base::doit"
--------------------------------------------------------------------------
% python testcheckpoint1.py
Base::doit
Base::doit
checkpointed here
Base::doit
% python testresume.py
resuming...
Base::doit
--------------------------------------------------------------------------
In testcheckpoint2.py I pass a derived Python class to Caller. The
derived doit method displays "Derived:doit" and a counter:
--------------------------------------------------------------------------
% python testcheckpoint2.py
Derived::doit count = 1
Derived::doit count = 2
checkpointed here
Derived::doit count = 3
% python testresume.py
resuming...
Derived::doit count = 3
--------------------------------------------------------------------------
In testcheckpoint3,py, I use a derived Python class with a non-trivial
constructor, which is used to set the initial value of the counter:
--------------------------------------------------------------------------
% python testcheckpoint3.py
Derived2::doit count = 5
Derived2::doit count = 6
checkpointed here
Derived2::doit count = 7
% python testresume.py
resuming...
Derived2::doit count = 7
--------------------------------------------------------------------------
As you can see, many things are possible with the code the way it
currently exists. I would still like to figure out how to preserve the
state of the base class. Any and all advice will be appreciated.
--Jim Amundson
<http://stackoverflow.com/questions/7289897/boost-serialization-and-boost-python-two-way-pickle>
#include <iostream>
#include <fstream>
#include <boost/python.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/export.hpp>
class Base
{
public:
Base()
{
}
virtual void
doit()
{
std::cout << "Base::doit" << std::endl;
}
template<class Archive>
void
serialize(Archive & ar, const unsigned int version)
{
}
virtual
~Base()
{
}
};
typedef boost::shared_ptr<Base > Base_sptr;
class Caller
{
private:
Base_sptr callee;
public:
Caller(Base_sptr to_call) :
callee(to_call)
{
}
// default constructor needed for serialization
Caller()
{
}
void
call()
{
callee->doit();
}
template<class Archive>
void
serialize(Archive & ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_NVP(callee);
}
~Caller()
{
}
};
void
checkpoint(Caller const& caller, std::string const& filename)
{
std::ofstream output_stream(filename.c_str());
boost::archive::xml_oarchive output_archive(output_stream);
output_archive << BOOST_SERIALIZATION_NVP(caller);
output_stream.close();
}
void
resume(std::string const& filename)
{
std::ifstream input_stream(filename.c_str());
boost::archive::xml_iarchive input_archive(input_stream);
Caller caller;
input_archive >> BOOST_SERIALIZATION_NVP(caller);
input_stream.close();
caller.call();
}
using namespace boost::python;
struct Base_callback : Base
{
Base_callback(PyObject * p) :
Base(), self(handle< > (borrowed(p)))
{
}
Base_callback(PyObject * p, const Base & x) :
Base(x), self(handle< > (borrowed(p)))
{
}
// default constructor needed for serialization
Base_callback() :
Base(), self()
{
}
void
doit()
{
call_method<void > (self.ptr(), "doit");
}
static void
default_doit(Base & self_)
{
self_.Base::doit();
}
template<class Archive>
void
save(Archive & ar, const unsigned int version) const
{
ar &
BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
std::string pickled_object(
extract<std::string > (
import("cPickle").attr("dumps")(self)));
ar & BOOST_SERIALIZATION_NVP(pickled_object);
}
template<class Archive>
void
load(Archive & ar, const unsigned int version)
{
ar &
BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
std::string pickled_object;
ar & BOOST_SERIALIZATION_NVP(pickled_object);
str pickle_str(pickled_object);
self = import("cPickle").attr("loads")(pickle_str);
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
private:
object self;
};
BOOST_CLASS_EXPORT_GUID(Base_callback, "Base_callback")
BOOST_PYTHON_MODULE(mymodule)
{
class_<Base, Base_callback > base("Base", init<>());
base.def("doit", &Base_callback::default_doit);
base.enable_pickling();
class_<Caller > caller("Caller", init<Base_sptr >());
caller.def("call", &Caller::call);
def("checkpoint", checkpoint);
def("resume", resume);
}
#!/usr/bin/env python
import mymodule
b = mymodule.Base()
c = mymodule.Caller(b)
c.call()
c.call()
mymodule.checkpoint(c, "checkpoint.xml")
print "checkpointed here"
c.call()
#!/usr/bin/env python
import mymodule
import derivedmodule
d = derivedmodule.Derived()
c = mymodule.Caller(d)
c.call()
c.call()
mymodule.checkpoint(c, "checkpoint.xml")
print "checkpointed here"
c.call()
#!/usr/bin/env python
import mymodule
class Derived(mymodule.Base):
def __init__(self):
mymodule.Base.__init__(self)
self.count = 0
def doit(self):
self.count += 1
print "Derived::doit count =", self.count
class Pickle_helper:
__getstate_manages_dict__ = 1
def __init__(self, *args):
self.args = args
def __getinitargs__(self):
return self.args
def __getstate__(self):
return self.__dict__
def __setstate__(self, state):
self.__dict__ = state
class Derived2(mymodule.Base, Pickle_helper):
def __init__(self, initial_count):
mymodule.Base.__init__(self)
Pickle_helper.__init__(self, initial_count)
self.count = initial_count
def doit(self):
self.count += 1
print "Derived2::doit count =", self.count
#!/usr/bin/env python
import mymodule
import derivedmodule
d = derivedmodule.Derived2(4)
c = mymodule.Caller(d)
c.call()
c.call()
mymodule.checkpoint(c, "checkpoint.xml")
print "checkpointed here"
c.call()
#!/usr/bin/env python
import mymodule
print "resuming..."
mymodule.resume("checkpoint.xml")
all: mymodule.so
mymodule.so: mymodule.cc
g++ -I/usr/include/python2.7 -shared -fPIC -o mymodule.so mymodule.cc
-lboost_python -lboost_serialization
clean:
/bin/rm -f *.o *.so
_______________________________________________
Cplusplus-sig mailing list
Cplusplus-sig@python.org
http://mail.python.org/mailman/listinfo/cplusplus-sig