Cython与深拷贝及引用方法的困扰,有什么替代方案吗?

15 投票
2 回答
3460 浏览
提问于 2025-04-17 03:44

最近我在玩Cython,想要提高速度,但我的项目继承了一个模块,这个模块有一个叫做copy()的方法,它使用了deepcopy()。我尝试在重写的copy()版本中实现deepcopy(),我以为我搞定了,但现在似乎又不行了。

TypeError: object.__new__(cython_binding_builtin_function_or_method) is not safe,
   use cython_binding_builtin_function_or_method.__new__()

这个问题出现在python/lib/copy_reg.py的这里:

return cls.__new__(cls, *args)

我用的是Python 2.7。有没有可能更新版本的Python在deepcopy()时以一种“安全”的方式返回?我也在用最新版本的Cython,0.15.1。

更新3

注意,我已经删除了之前的更新,以保持内容尽可能简单。

好的!我想我找到了不兼容的地方,但我不知道该怎么处理。

class CythonClass:
    def __init__(self):
        self._handle = self._handles.get("handle_method")

    def call_handle(self):
        self._handle(self)

    def handle_method(self):
        print "I'm a little handle!"

    handles = {"handle_method", handle_method}

然后在我的主应用程序中:

from cython1 import CythonClass
from copy import deepcopy

if __name__ == "__main__":
    gc1 = CythonClass()
    gc1.call_handle()
    gc2 = deepcopy(gc1)

我得到了:

I'm a little handle!

Traceback (most recent call last):
  File "cythontest.py", line 8, in <module>
    gc2 = deepcopy(gc1)
  File "C:\python26\lib\copy.py", line 162, in deepcopy
    y = copier(x, memo)
  File "C:\python26\lib\copy.py", line 292, in _deepcopy_inst
    state = deepcopy(state, memo)
  File "C:\python26\lib\copy.py", line 162, in deepcopy
    y = copier(x, memo)
  File "C:\python26\lib\copy.py", line 255, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "C:\python26\lib\copy.py", line 189, in deepcopy
    y = _reconstruct(x, rv, 1, memo)
  File "C:\python26\lib\copy.py", line 323, in _reconstruct
    y = callable(*args)
  File "C:\python26\lib\copy_reg.py", line 93, in __newobj__
    return cls.__new__(cls, *args)
TypeError: object.__new__(cython_binding_builtin_function_or_method) is not safe, use cython_binding_builtin_function_or_method.__new__()

关键在于函数/句柄引用:

handles = {"handle_method", handle_method}

如果我不包括方法/函数引用,Cython在执行deepcopy时不会出错。如果我包括了,它就不喜欢deepcopy/copy_reg是如何复制引用的。

除了不使用方法/函数引用,还有其他想法吗?如果这是简单的解决方案,我还有一些事情需要理清。(我已经在处理这个问题,同时完成这段文字)

谢谢!

2 个回答

9

找到了这个:

"深拷贝在Cython中工作正常吗?"

不。在这种情况下(你使用的是扩展类型,也就是cdef类),你需要为你的类实现pickle协议。http://docs.python.org/library/pickle.html#pickling-and-unpickling-extension-types

来自这里:https://groups.google.com/forum/#!topic/cython-users/p2mzJrnOH4Q

在链接的文章中提到的“实现pickle协议”其实很简单,轻松解决了我的问题(虽然我做的事情稍微有点不同——我的类是cdef class,而且我在里面存储了一个指向CPP对象的指针,这个指针不能简单地复制——我不知道这是否能解决上面提到的Python继承问题,但肯定值得一试。)

总之,实现pickle协议是很简单的(下面的例子使用的是“C++ cython”,对于del关键字有双重含义,还有其他一些内容):

cdef class PyObject(object):
    cdef CppObject* cpp
    cdef object arg1
    cdef object arg2

    def __cinit__(self, arg1=[], arg2=False):
        # C++ constructor using python values, store result in self.cpp.

        # new code: cache the python arguments that were used.
        self.arg1 = arg1
        self.arg2 = arg2

    def __init__(self, arg1=[], arg2=False):
        # logic for validating arguments.
        pass

    def __dealloc__(self):
        if not self.cpp == NULL:
            del self.cpp

    def __reduce__(self):
        # a tuple as specified in the pickle docs - (class_or_constructor, 
        # (tuple, of, args, to, constructor))
        return (self.__class__, (self.arg1, self.arg2))

当我尝试这个时,我可以在一个包含我的Cython扩展类型实例的字典上调用copy.deepcopy(),并得到一个新的字典,里面包含一个新的实例(在终端打印时内存地址不同)。之前相同的代码会导致程序崩溃。

9

啊,终于找到了“自己回答问题”的按钮。

我可能有点急,但因为还没有人回复(我意思是,谁会在周四下午用Cython并回答问题呢),所以我想把这个问题结束掉。

1) Cython不喜欢在有函数或方法引用的类上使用深拷贝。那些变量的拷贝会失败。根据我的了解,似乎没有什么办法可以解决这个问题,你只能想出一个新的设计,不需要用到它们。我最后在我的项目中用上面的代码做了这样的调整。

2) Cython根本不支持属性装饰器。你不能使用@property@<属性名>.setter。属性需要用传统的方法来设置。比如说,<属性名> = property(get_property, set_property)

3) 继承的非Cython类的方法“可能”无法访问。我知道这说得不太清楚,我也不能完全解释。我要说的是,我在继承NetworkX.DiGraph时,number_of_nodes()在Cython中无法访问,而在纯Python中是可以的。我不得不创建一个方法的引用来使用它。比如说,number_of_verts = NetworkX.DiGraph.number_of_nodes

撰写回答