使用 Cython 扩展模块包装 std::vector - 如何编写 __setitem__() 方法?

Using Cython extension module to wrap std::vector - How do I program __setitem__() method?

这似乎是一个应该有明显答案的问题,但出于某种原因我无法在网上找到任何示例。

我正在使用 Cython 在 Python class 中包装 C++ 对象的向量。我还有一个已编码的 C++ class 的 Cython 包装器。我可以使用 __len__()__getitem__()resize() 等几种方法正常工作,但 __setitem__() 方法给我带来了问题。

为简单起见,我使用 ints 的向量编写了一个小示例。我想如果我能让这段代码工作,那么我可以以此为基础为我的 C++ class 获得解决方案。

MyPyModule.pyx

# distutils: language = c++

from libcpp.vector cimport vector
from cython.operator cimport dereference as deref

cdef class MyArray:
    cdef vector[int]* thisptr
    def __cinit__(self):
        self.thisptr = new vector[int]()

    def __dealloc__(self):
        del self.thisptr

    def __len__(self):
        return self.thisptr.size()

    def __getitem__(self, size_t key):
        return self.thisptr.at(key)

    def resize(self, size_t newsize):
        self.thisptr.resize(newsize)

    def __setitem__(self, size_t key, int value):
        # Attempt 1:
        # self.thisptr.at(key) = value

        # Attempt 2:
        # cdef int* itemptr = &(self.thisptr.at(key))
        # itemptr[0] = value

        # Attempt 3:
        # (self.thisptr)[key] = value

        # Attempt 4:
        self[key] = value

当我尝试使用尝试 1 进行 cythonize 时,出现错误 Cannot assign to or delete this。当我尝试尝试 2 时,创建了 .cpp 文件,但编译器抱怨说:

error: cannot convert ‘__Pyx_FakeReference<int>*’ to ‘int*’ in assignment
   __pyx_v_itemptr = (&__pyx_t_1);

在尝试 3 中,Cython 不会构建文件,因为 Cannot assign type 'int' to 'vector[int]'。 (当我用 C++ 对象而不是 int 尝试这种样式时,它会抱怨,因为我有一个引用作为左值。)尝试 4 编译,但是当我尝试使用它时,我得到了一个段错误。

Cython docs 说不支持将引用作为左值返回,这很好——但我该如何绕过它,以便我可以为我的向量之一分配一个新值元素?

您可以让 Cython 自己为您处理,而不是尝试处理来自 Cython 代码的指针:

cdef class MyArray:
    cdef vector[int] thisptr

    def __len__(self):
        return self.thisptr.size()

    def __getitem__(self, size_t key):
        return self.thisptr[key]

    def __setitem__(self, size_t key, int value):
        self.thisptr[key] = value

    def resize(self, size_t newsize):
        self.thisptr.resize(newsize)

这种做法有什么问题吗?

通过指针访问向量有两种方式,

def __setitem__(self, size_t key, int value):
    deref(self.thisptr)[key] = value
    # or
    # self.thisptr[0][key] = value

Cython 将这两种情况翻译如下:

Python: deref(self.thisptr)[key] = value
C++:    ((*__pyx_v_self->thisptr)[__pyx_v_key]) = __pyx_v_value;

Python: self.thisptr[0][key] = value
C++:    ((__pyx_v_self->thisptr[0])[__pyx_v_key]) = __pyx_v_value;

它们是等价的,即访问相同的矢量对象。

我已经接受了J.J。 Hakala 的回答(非常感谢!)。我调整了该方法以包含 out-of-bounds 检查,因为它使用 [] 运算符而不是 at() 方法:

cdef class MyArray:
    (....)
    def __setitem__(self, size_t key, int value):
        if key < self.thisptr.size():
            deref(self.thisptr)[key] = value
        else:
            raise IndexError("Index is out of range.")