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

Reply via email to