在Python中,我有一对类似的类
class Internal:
def __init__(self, ref):
self.ref = ref
class External:
def __init__(self):
self.storage = [1,2,3]
self.int = Internal(self.storage)
def change(self):
self.storage[1] = 10
External
的深度复制效果非常好
from copy import deepcopy
s = External()
s1 = deepcopy(s)
s.change()
print(s.int.ref) # [1,10,3]
print(s1.int.ref) # [1,2,3]
我想使用Python C扩展来实现
Internal
。
据我了解,我必须实现 __reduce__
或 __deepcopy__
。我决定和__deepcopy__
一起去。目前,我将其实现为
PyObject* internal_deepcopy(InternalObj* self, PyObject* memo) {
InternalObj* obj = reinterpret_cast<InternalObj*>(PyType_GenericNew(Py_TYPE(self), nullptr, nullptr));
if (!obj) return nullptr;
obj->ref = self->ref;
Py_INCREF(obj->ref);
return reinterpret_cast<PyObject*>(obj);
}
显然,这是错误的。如果我使用这样的深度复制,那么我不会在复制的
storage
中获得对新 External
的新引用。
from internals import Internal
class External:
def __init__(self):
self.storage = [1,2,3]
self.int = Internal(self.storage)
def change(self):
self.storage[1] = 10
s = External()
s1 = deepcopy(s)
s.change()
s.int.print() # 1 10 3
s1.int.print() # 1 10 3
即物体没有分离。
问题:如何跟踪
External
类的复制并设置对其在 Internal
中存储的正确引用(就像它在纯 Python 代码中一样)?
我的Python模块的其他部分
#include <Python.h>
#include <iostream>
struct InternalObj {
PyObject_HEAD
PyObject* ref;
};
PyObject* internal_print(InternalObj* self, PyObject* unused) {
const int size = PySequence_Fast_GET_SIZE(self->ref);
PyObject** items = PySequence_Fast_ITEMS(self->ref);
for (int i{}; i < size; ++i) std::cout << PyLong_AsLong(items[i]) << ' ';
std::cout << std::endl;
return Py_None;
}
PyMethodDef internal_methods[]{
{"__deepcopy__", (PyCFunction)internal_deepcopy, METH_O, 0},
{"print", (PyCFunction)internal_print, METH_NOARGS, 0},
{nullptr, nullptr},
};
void internal_dealloc(InternalObj* self) {
Py_DECREF(self->ref); // release
Py_TYPE(self)->tp_free(self);
}
PyObject* internal_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) {
PyObject* ref;
const char* kwlist[]{"ref", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:__init__", const_cast<char**>(kwlist), &ref)) return nullptr;
InternalObj* obj = reinterpret_cast<InternalObj*>(PyType_GenericNew(subtype, nullptr, nullptr));
if (!obj) return nullptr;
obj->ref = ref;
Py_INCREF(ref); // capture
return reinterpret_cast<PyObject*>(obj);
}
PyTypeObject internal_type{
PyVarObject_HEAD_INIT(nullptr, 0)
"internals.Internal", // tp_name
sizeof(InternalObj), // tp_basicsize
0, // tp_itemsize
(destructor)internal_dealloc, // tp_dealloc
0, // tp_vectorcall_offset
0, // tp_getattr
0, // tp_setattr
0, // tp_as_async
0, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
0, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
0, // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
internal_methods, // tp_methods
0, // tp_members
0, // tp_getset
0, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
0, // tp_init
0, // tp_alloc
internal_new, // tp_new
};
PyModuleDef internals_module{
PyModuleDef_HEAD_INIT,
"internals",
"Python interface for internals",
-1,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr
};
PyMODINIT_FUNC PyInit_internals() {
PyObject *module = PyModule_Create(&internals_module);
if (PyType_Ready(&internal_type) < 0) return nullptr;
PyModule_AddObject(module, "Internal", Py_NewRef(&internal_type));
return module;
}
我用来构建的介子脚本。
project('ints', 'c', 'cpp', version: '0.1', default_options: ['c_std=c18', 'cpp_std=c++20', 'b_ndebug=if-release'])
py_installation = import('python').find_installation('python3', required: true)
py_installation.extension_module('internals', 'internal.cpp', dependencies: py_installation.dependency())
看起来很吓人,但技术上很简单。 答案有两部分。
l = []
l.append(l)
q = deepcopy(l)
工作没有问题。
deepcopy
以 memo
的形式暂时记住第二个参数 {id(old): new}
中复制的对象。
如果对象已被处理,deepcopy
返回从 memo
创建的副本。
如果没有,它会创建一个新对象以及 deepcopy
它的所有引用。
导入
deepcopy
(在我的情况下,这部分可以减少,因为我知道我的ref
必须已经在备忘录中,但万一不是)
PyObject *copy = PyImport_ImportModule("copy");
deepcopy = PyObject_GetAttrString(copy, "deepcopy");
因为
list
(以及许多其他对象)没有 __deepcopy__
。它的深拷贝是在Python的copy
模块中实现的。我发现有些人并不害怕在深层复制中导入,但我建议在模块 init 中进行导入(在我的例子中是 PyInit_internals)。
实现正确版本的深复制)))
PyObject* internal_deepcopy(InternalObj* self, PyObject* memo) {
PyObject* id = PyLong_FromLong(static_cast<long>(self)); // need this id as an object to interact with `memo`
if (!id) return nullptr;
if (memo && memo != Py_None) {
PyObject* memed = PyDict_GetItem(memo, id);
if (memed) {
Py_DECREF(id);
return memed;
}
Py_INCREF(memo); // to unify exit code with next branch where `memo` is created.
} else memo = PyDict_New(); // top-level call
InternalObj* obj = reinterpret_cast<InternalObj*>(PyType_GenericNew(Py_TYPE(self), nullptr, nullptr)); // create copy of internals of this object
if (!obj) {
Py_DECREF(id);
Py_DECREF(memo);
return nullptr;
}
if (PyDict_SetItem(memo, id, reinterpret_cast<PyObject*>(obj)) < 0) { // update `memo`
Py_DECREF(id);
Py_DECREF(memo);
Py_DECREF(obj);
return nullptr;
}
Py_DECREF(id);
obj->ref = PyObject_CallFunctionObjArgs(deepcopy, self->ref, memo, nullptr); // call deepcopy for `ref`
Py_DECREF(memo); // delete map if it was created in this deepcopy
if (!obj->ref) {
Py_DECREF(obj);
return nullptr;
}
return reinterpret_cast<PyObject*>(obj); // return copied object.
}
我不确定,上面的代码涵盖了一般情况。例如,在
copy.deepcopy
中,我看到了一些对对象生存时间的操作,但这里遗漏了。但对于简单的情况,它应该是正确的。