Bra*_*mon 5 python numpy cython
我在Pandas 源代码中看到过几次这样的事情:
def nancorr(ndarray[float64_t, ndim=2] mat, bint cov=0, minp=None):
# ...
N, K = (<object> mat).shape
Run Code Online (Sandbox Code Playgroud)
这意味着ndarray调用的 NumPymat被类型转换为 Python 对象。*
经过进一步检查,似乎使用了这个,因为如果不是,则会出现编译错误。我的问题是:为什么首先需要这种类型转换?
这里有一些例子。 这个答案只是表明元组打包在 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
Run Code Online (Sandbox Code Playgroud)
但同样,问题本身似乎不是元组解包。这将失败并出现相同的错误。
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
Run Code Online (Sandbox Code Playgroud)
看起来,这里没有发生元组解包。元组是第一个参数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
Run Code Online (Sandbox Code Playgroud)
这也有效(也许是最令人困惑的):
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
Run Code Online (Sandbox Code Playgroud)
例子:
>>> 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.]]))
Run Code Online (Sandbox Code Playgroud)
*也许这与arr成为内存视图有关?但那是黑暗中的一击。
另一个例子是在 Cython文档中:
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]
Run Code Online (Sandbox Code Playgroud)
在这种情况下,简单的索引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
Run Code Online (Sandbox Code Playgroud)
你是对的,它与 Cython 下的元组拆包无关。
原因是,这cnp.ndarray不是一个通常的 numpy 数组(这意味着具有 Python 已知接口的 numpy 数组),而是numpy 的 C 实现的Cython 包装器PyArrayObject(在 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
Run Code Online (Sandbox Code Playgroud)
shape实际上映射到底层 C 结构的dimensions-field(npy_intp *shape "dimensions"而不是简单地)。npy_intp *dimensions这是一个技巧,所以可以写
mat.shape[0]
Run Code Online (Sandbox Code Playgroud)
它的外观(以及某种程度上的感觉)就像shape调用了 numpy 的 python-property 一样。但实际上,我们采取了直接通向底层 C 结构的捷径。
顺便说一句,调用 python- 的shape成本相当高:必须创建一个元组并用 中的值填充dimensions,然后访问第 0 个元素。另一方面,Cython 的做法要便宜得多 - 只需访问正确的元素即可。
但是,如果您还想访问数组的 python 属性,则必须将其转换为普通的 python 对象(即忘记这是 a ndarray),然后shape通过通常的 Python 机制解析为元组属性调用。
所以基本上,即使这很方便,您也不希望像 pandas 代码中那样以紧密循环的方式访问 numpy 数组的维度,而是为了性能而执行更详细的变体:
...
N=mat.shape[0]
K=mat.shape[1]
...
Run Code Online (Sandbox Code Playgroud)
为什么你可以object[cnp.float64_t]在函数签名中编写或类似的内容让我觉得很奇怪 - 然后参数显然被解释为一个简单的对象。也许这只是一个错误。