如何在 C 扩展中正确深度复制链接对象

问题描述 投票:0回答:1

在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())
python-c-api python-extensions
1个回答
0
投票

看起来很吓人,但技术上很简单。 答案有两部分。

  1. Python深拷贝是如何循环的? 例如:
l = []
l.append(l)
q = deepcopy(l)

工作没有问题。

deepcopy
memo
的形式暂时记住第二个参数
{id(old): new}
中复制的对象。 如果对象已被处理,
deepcopy
返回从
memo
创建的副本。 如果没有,它会创建一个新对象以及
deepcopy
它的所有引用。

  1. 这应该如何应用在C API中?

导入

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
中,我看到了一些对对象生存时间的操作,但这里遗漏了。但对于简单的情况,它应该是正确的。

© www.soinside.com 2019 - 2024. All rights reserved.