Cython:为什么NumPy数组需要被类型转换为object?

2024-10-01 00:26:14 发布

您现在位置:Python中文网/ 问答频道 /正文

我在Pandas source上见过几次这样的东西:

def nancorr(ndarray[float64_t, ndim=2] mat, bint cov=0, minp=None):
    # ...
    N, K = (<object> mat).shape

这意味着名为mat的NumPy ndarraytype-casted的Python对象。*

经过进一步的检查,似乎使用这种方法是因为如果不是这样,就会出现编译错误。我的问题是:为什么首先需要这种类型转换?在

这里有几个例子。This答案很简单,元组打包在Cython中不像在Python中那样有效——但这似乎不是元组解包的问题。(不管怎样,这都是一个很好的答案,我不想挑刺。)

执行以下脚本,shape.pyx。它将在编译时失败,并出现“Cannot convert'npy_intp*”to Python object“

^{pr2}$

但是,问题本身似乎不是tuple解包。这将以相同的错误失败。在

def test_castobj(ndarray[float64_t, ndim=2] arr):

    cdef:
        # Py_ssize_t b1, b2
        ndarray[float64_t, ndim=2] zeros

    zeros = np.zeros(arr.shape, dtype=np.float64)
    return zeros

看起来,这里没有元组解包。元组是np.zeros的第一个参数。在

def test_castobj(ndarray[float64_t, ndim=2] arr):
    """This works"""
    cdef:
        Py_ssize_t b1, b2
        ndarray[float64_t, ndim=2] zeros

    b1, b2 = (<object> arr).shape
    zeros = np.zeros((<object> arr).shape, dtype=np.float64)
    return b1, b2, zeros

这也很管用(也许是最令人困惑的):

def test_castobj(object[float64_t, ndim=2] arr):
    cdef:
        tuple shape = arr.shape
        ndarray[float64_t, ndim=2] zeros
    zeros = np.zeros(shape, dtype=np.float64)
    return zeros

示例:

>>> from shape import test_castobj
>>> arr = np.arange(6, dtype=np.float64).reshape(2, 3)

>>> test_castobj(arr)
(2, 3, array([[0., 0., 0.],
        [0., 0., 0.]]))

或许这与arr是一个记忆视图有关?但在黑暗中


另一个例子是Cythondocs

cpdef int sum3d(int[:, :, :] arr) nogil:
    cdef size_t i, j, k
    cdef int total = 0
    I = arr.shape[0]
    J = arr.shape[1]
    K = arr.shape[2]

在这种情况下,简单地索引arr.shape[i]就可以防止这个错误,我觉得很奇怪。在

这也适用于:

def test_castobj(object[float64_t, ndim=2] arr):
    cdef ndarray[float64_t, ndim=2] zeros
    zeros = np.zeros(arr.shape, dtype=np.float64)
    return zeros

Tags: testobjectdefnpzerosb1元组ndarray
1条回答
网友
1楼 · 发布于 2024-10-01 00:26:14

你说得对,这和Cython下的元组解包无关。在

原因是,cnp.ndarray不是一个普通的numpy数组(这意味着一个具有python已知接口的numpy数组),而是一个numpy的C实现的^{}(在python中称为np.array):

ctypedef class numpy.ndarray [object PyArrayObject]:
    cdef __cythonbufferdefaults__ = {"mode": "strided"}

    cdef:
        # Only taking a few of the most commonly used and stable fields.
        # One should use PyArray_* macros instead to access the C fields.
        char *data
        int ndim "nd"
        npy_intp *shape "dimensions"
        npy_intp *strides
        dtype descr
        PyObject* base

实际上,shape映射到底层C-stuct的^{}-fieldnpy_intp *shape "dimensions"),而不是简单的npy_intp *dimensions)。这是个把戏,所以你可以写作

^{pr2}$

它的外观(以及某种程度上的感觉)就像numpy的python属性shape被调用一样。但事实上,一条直接通向潜在的C-stuct的捷径就走了。在

顺便说一句,调用python-shape代价很高:必须创建一个元组并用dimensions中的值填充,然后访问第0个元素。另一方面,Cython的方法要便宜得多——只要使用正确的元素。在

但是,如果您还想访问数组的python属性,则必须将其强制转换为普通python对象(即,忘记这是一个ndarray),然后通过通常的python机制将{}解析为tuple属性调用。在

因此,基本上,即使这很方便,您也不希望像pandas代码中那样在一个紧循环中访问numpy数组的维度,而是使用更详细的变量来提高性能:

^{3}$

为什么你能在函数签名中写object[cnp.float64_t]或类似的东西,我觉得很奇怪——参数很明显被解释为一个简单的对象。也许这只是个虫子。在

相关问题 更多 >