You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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);
}

说明

  1. 深拷贝ref对象:调用PyDeepCopy函数对self->ref进行深拷贝,这会遵循Python内置的深拷贝逻辑,比如对列表创建新的副本。
  2. 利用memo字典:memo字典会记录已经完成深拷贝的对象,当External的storage被拷贝后,Internal的深拷贝会直接使用memo中已有的拷贝结果,保证引用关系和纯Python一致。
  3. 正确创建实例:通过PyObject_CallObject调用Internal的构造函数,确保新实例的初始化逻辑和Python端一致,避免直接手动赋值可能带来的引用计数错误。

修改后重新编译扩展,执行测试代码即可得到和纯Python相同的结果:修改原External对象的storage后,副本的Internal实例引用的是独立的拷贝,不会受到影响。

内容的提问来源于stack exchange,提问作者Askold Ilvento

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 12:56:12