[C++-sig] Properly copying wrapped classes
Jay Riley
super24bitsound at hotmail.com
Fri Jun 1 02:49:34 CEST 2012
I'm having a problem with some python objects derived in python. I have a Clone method that's supposed to make a full copy of these python inherited objects, but it isn't working. The problem is my wrapper class' PyObject* self isn't getting copied, so when I use call_method<>(self, "method name"), self will never point to the copied instance. Here's a brief example showing the issue
C++ file:
#include <boost/python.hpp>#include <scripting/Python/ScriptHelpers.h>using namespace boost::python;
class BaseClass{public: typedef boost::shared_ptr<BaseClass> ClonePtr; BaseClass() : ID(++IDCounter) {
} BaseClass(const BaseClass& cp) : ID(++IDCounter) {
} virtual boost::shared_ptr<BaseClass> Clone() { return boost::shared_ptr<BaseClass>(new BaseClass(*this)); } virtual int GetID() const { return ID; } virtual void Test() { std::cout << "C++ side ID: " << ID << "\n"; }protected: int ID;private: static int IDCounter;};
class BaseClassWrap : public BaseClass{public: BaseClassWrap(PyObject* self_) : self(self_) {
} BaseClassWrap(PyObject* self_, const BaseClass& cp) : self(self_), BaseClass(cp) {
} BaseClassWrap(PyObject* self_, const BaseClassWrap& cp) : self(self_), BaseClass(cp) {
}
virtual int GetID() const override { return call_method<int>(self, "GetID"); } virtual void Test() { call_method<void>(self, "Test"); } void TestDefault() { BaseClass::Test(); } int GetIDDefault() const { return BaseClass::GetID(); }
boost::shared_ptr<BaseClass> Clone() override { return call_method<ClonePtr>(self, "Clone"); } boost::shared_ptr<BaseClass> CloneDefault() const { return boost::shared_ptr<BaseClass>(new BaseClassWrap(*this)); } PyObject* self;private:};
BOOST_PYTHON_MODULE(TestModule){ class_<BaseClass, boost::shared_ptr<BaseClassWrap> >("BaseClass", init<>()) .def(init<const BaseClass&>()) .def(init<const BaseClassWrap&>()) .def("GetID", &BaseClassWrap::GetIDDefault) .def("Clone", &BaseClassWrap::CloneDefault) .def("Test", &BaseClassWrap::TestDefault) .def("__copy__", &generic__copy__< BaseClassWrap >) .def("__deepcopy__", &generic__deepcopy__< BaseClassWrap >) ;}
int BaseClass::IDCounter = 0;
int main(){ PyImport_AppendInittab("TestModule", initTestModule); Py_Initialize(); try { auto EngineModule = object( (handle<>(PyImport_ImportModule("TestModule"))) ); auto MainModule = object((handle<>(borrowed(PyImport_AddModule("__main__"))))); auto MainNamespace = MainModule.attr("__dict__"); MainNamespace["TestModule"] = EngineModule;
object result = exec_file("D:\\FinalReport\\Test.py", MainNamespace, MainNamespace);
object test = MainNamespace["test"];
boost::shared_ptr<BaseClass> basec = extract<boost::shared_ptr<BaseClass> >(test());
if (basec.get() != nullptr) { auto cl = basec->Clone(); basec->Test(); cl->Test(); std::cout << "Original ID " << basec->GetID() << "\n"; std::cout << "Clone ID " << cl->GetID() << "\n"; } } catch (boost::python::error_already_set) { PyErr_Print(); }
return 0;}
generic__copy__ and generic__deep__copy are in ScriptHelpers and shown below. They were pulled from an old thread on this mailing list and look like:
#define PYTHON_ERROR(TYPE, REASON) \ { \ PyErr_SetString(TYPE, REASON); \ throw error_already_set(); \ }
template<class T> inline PyObject * managingPyObject(T *p) { return typename manage_new_object::apply<T *>::type()(p); }
template<class Copyable> object generic__copy__(object copyable) { Copyable *newCopyable(new Copyable(extract<const Copyable &>(copyable))); object result(boost::python::detail::new_reference(managingPyObject(newCopyable)));
extract<dict>(result.attr("__dict__"))().update( copyable.attr("__dict__"));
return result; }
template<class Copyable> object generic__deepcopy__(object copyable, dict memo) { object copyMod = import("copy"); object deepcopy = copyMod.attr("deepcopy");
Copyable *newCopyable(new Copyable(extract<const Copyable &>(copyable))); object result(boost::python::detail::new_reference(managingPyObject(newCopyable)));
// HACK: copyableId shall be the same as the result of id(copyable) //in Python - // please tell me that there is a better way! (and which ;-p) int copyableId = (int)(copyable.ptr()); memo[copyableId] = result;
extract<dict>(result.attr("__dict__"))().update( deepcopy(extract<dict>(copyable.attr("__dict__"))(), memo));
return result; }
and the python file:
from TestModule import *import copy
class Der(BaseClass): def Test(self): print "Python Side Test. ID: " + str(self.GetID()) def Clone(self): print "Python Clone" return copy.deepcopy(self)
def test(): return Der()
Sorry about the length, just want to make sure I got everything relevant in. When I run this code, it prints:
Python Clone
Python Side Test. ID: 1Python Side Test. ID: 1Original ID 1Clone ID 1
this is wrong because Clone ID should be 2 (inspecting the object confirms it is 2). Like I said I'm pretty sure the problem is that the wrapper class' copy constructor only makes a copy of the PyObject* self, not a full copy. This is how every reference doc I saw was doing class wrapping, but isn't this wrong? Should we be making a full copy of the PyObject*? How would I go about doing that, or otherwise modifying my classes to get the behaviour I expect.
Thanks
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cplusplus-sig/attachments/20120531/eb0c0a2c/attachment.html>
More information about the Cplusplus-sig
mailing list