boost::python与深拷贝

前言

在python中,复杂对象例如list/dict/object在用=赋值的时候是进行的“浅拷贝”,如果要进行“深拷贝”,需要使用copy库提供的deepcopy函数:
class A :
    pass
a = A()
a.c = 1
b = copy.deepcopy(a)
现在我们希望在C++里面也能做到这一点,也即:对一个boost::python::object对象进行深拷贝。

方案探索

方案一:copy成员函数

因为boost::python::dict是有一个名叫copy的成员函数的,所以我们很自然地希望boost::python::object也能如此简单地实现深拷贝。然而这个成员函数在boost::python::object身上并不存在。

方案二:拷贝构造函数

boost::python::object的拷贝构造函数是否能“新建”一个对象呢?
boost::python::object b = boost::python::object(a);
编译没有问题,然而修改b.c的值时a.c还是改变了。可见python的“万物皆引用”的思路在boost::python中也得到了很好的贯彻。
以上是在讽刺。

方案三:新建对象后赋值

理论上这个跟使用拷贝构造函数没什么区别:
boost::python::object b;
b = a;
没抱什么希望,结果也的确如此。 

方案四:重构新对象

前面的都是瞎折腾,至此才是认真的探索。
我们这次要处理的其实是一个object,不过list/dict也一样:既然boost::python不提供深拷贝,那么我们就把这个object的内容拆开来,把它的成员在新对象上再挨个重建起来,是不是就OK了?
boost::python::object a;
::PyObject_SetAttrString(a, "c", ::PyObject_GetAttrString(b, "c"));
还是不行。上面的代码有两个问题:
  1. a最后是一个NoneType;
  1. b.c还是一个a.c的引用。
第一个问题还算有办法解决,第二个问题很致命。也就是说用GetAttr/SetAttr不管用,要重构就得把Attr变成具体的值(简单变量)。如果只是对着PyObject*操作,还是同一个对象的引用。
所以,这条路虽然没有完全失败,但是代价相当高。我们需要编写一个函数把PyObject完完全全地拆开,然后再度组装起来。并且这样做能否真的“还原”?对此我们并没有什么底气。

方案五:借用boost::python::dict::copy成员函数

我有一篇Blog中曾经提到一种方法,至少对于list是适用的:
「把一个list放到一个dict里面去copy()完再拿出来用」
涉及到类型转换的代码写起来有点麻烦,我就不贴了。总之结果是不理想的——这方案对于boost::python::object没用。因为dict的copy()成员函数是有局限性的:它只能处理一层数据的深拷贝,对于嵌套在里面的复杂对象是无能为力的。

方案六:调用python的copy.deepcopy()

看来没有捷径可走,最后还得回到真正的解决方案上来:既然python提供了copy库,用copy.deepcopy()可以实现完美的深拷贝,而我们又没有别的路可走,那么就在C++里面调用python来完成这件事情吧。
//  调用Python库copy.deepcopy()来完成深拷贝
//  注意:pyObject必须是能被pickle序列化的类型
boost::python::object CloneBPyObj(const boost::python::object& pyObject)
{
    PyGILState_STATE gstate;
    gstate = ::PyGILState_Ensure();

    //  导入Python脚本
    bpy::object pyResult;
    PyObject* pModule = nullptr;
    pModule = ::PyImport_ImportModule("copy");
    if (pModule && ::PyObject_HasAttrString(pModule, "deepcopy"))