[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