如何在Python C扩展中正确深度复制关联对象
问题:Python C扩展中Internal类的深拷贝引用问题
原Python实现与预期行为
在纯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]
C扩展实现的问题
尝试通过Python C扩展实现Internal类,当前的__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); }
该实现存在问题:复制External对象后,新对象的Internal实例仍引用原storage,导致修改原对象时副本也受影响:
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
完整C扩展代码
以下是当前的完整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; }
构建脚本(Meson)
用于编译扩展的Meson脚本:
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())
解决方案
要实现和纯Python一致的深拷贝效果,需要修改__deepcopy__方法,对self->ref进行深拷贝,并且利用传入的memo字典跟踪已拷贝的对象,避免重复拷贝。修改后的internal_deepcopy函数如下:
PyObject* internal_deepcopy(InternalObj* self, PyObject* memo) { // 对ref对象执行深拷贝,memo会自动跟踪已拷贝的对象 PyObject* copied_ref = PyDeepCopy(self->ref, memo); if (!copied_ref) { return nullptr; } // 构造参数,调用Internal的构造函数创建新实例 PyObject* args = PyTuple_New(1); if (!args) { Py_DECREF(copied_ref); return nullptr; } // 将copied_ref放入元组,转移所有权 PyTuple_SetItem(args, 0, copied_ref); // 创建新的Internal实例 InternalObj* obj = reinterpret_cast<InternalObj*>( PyObject_CallObject(reinterpret_cast<PyObject*>(&internal_type), args) ); Py_DECREF(args); return reinterpret_cast<PyObject*>(obj); }
说明
- 深拷贝ref对象:调用
PyDeepCopy函数对self->ref进行深拷贝,这会遵循Python内置的深拷贝逻辑,比如对列表创建新的副本。 - 利用memo字典:
memo字典会记录已经完成深拷贝的对象,当External的storage被拷贝后,Internal的深拷贝会直接使用memo中已有的拷贝结果,保证引用关系和纯Python一致。 - 正确创建实例:通过
PyObject_CallObject调用Internal的构造函数,确保新实例的初始化逻辑和Python端一致,避免直接手动赋值可能带来的引用计数错误。
修改后重新编译扩展,执行测试代码即可得到和纯Python相同的结果:修改原External对象的storage后,副本的Internal实例引用的是独立的拷贝,不会受到影响。
内容的提问来源于stack exchange,提问作者Askold Ilvento
相关产品推荐
相关产品推荐

