【问题标题】:Cython: Why do NumPy arrays need to be type-cast to object?Cython:为什么 NumPy 数组需要类型转换为对象?
【发布时间】:2018-08-08 12:31:40
【问题描述】:

我在Pandas source看到过几次这样的事情:

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

这意味着一个名为 mat 的 NumPy ndarray 是 Python 对象的 type-casted*

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

这里有几个例子。 This 答案只是表明元组打包在 Cython 中不像在 Python 中那样工作——但这​​似乎不是元组解包问题。 (无论如何,这是一个很好的答案,我不想挑剔它。)

采用以下脚本,shape.pyx。它会在编译时失败,并显示“无法将 'npy_intp *' 转换为 Python 对象。”

from cython cimport Py_ssize_t
import numpy as np
from numpy cimport ndarray, float64_t
cimport numpy as cnp
cnp.import_array()

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

    cdef:
        Py_ssize_t b1, b2

    # Tuple unpacking - this will fail at compile
    b1, b2 = arr.shape
    return b1, b2

但同样,问题本身似乎并不是元组解包。这将失败并出现相同的错误。

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 是一个内存视图有关?但这是在黑暗中的一枪。


另一个例子是 Cython docs:

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

【问题讨论】:

  • 我不知道 Cpython,但至少在 c 中,如果 arr.shape 返回一个指针(似乎),你(和 numpy)没有办法知道它的维度。

标签: python numpy cython


【解决方案1】:

你是对的,它与 Cython 下的元组解包无关。

原因是,cnp.ndarray 不是通常的 numpy-array(这意味着具有从 python 已知的接口的 numpy-array),而是 PyArrayObject 的 numpy 的 C 实现的 Cython wrapper(其中在 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 的dimensions-fieldnpy_intp *shape "dimensions" 而不是简单的npy_intp *dimensions)。这是一个技巧,所以可以写

mat.shape[0]

它的外观(在某种程度上也有感觉)就像调用了 numpy 的 python-property shape。但实际上采用了直接通往底层 C-stuct 的捷径。

顺便说一句,调用 python-shape 的成本非常高:必须创建一个元组并用来自dimensions 的值填充,然后访问第 0 个元素。另一方面,Cython 的做法要便宜得多 - 只需访问正确的元素。

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

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

...
N=mat.shape[0]
K=mat.shape[1]
...

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

【讨论】:

    猜你喜欢
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多