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 _______________________________________________
Cplusplus-sig mailing list
[email protected]
http://mail.python.org/mailman/listinfo/cplusplus-sig