【问题标题】:Cython: how to get the 'actual Python type' (type code/dtype) from C-level type [duplicate]Cython:如何从 C 级类型中获取“实际 Python 类型”(类型代码/dtype)[重复]
【发布时间】:2018-12-01 12:39:32
【问题描述】:

我想为使用 ctypedef 定义的 memoryview 分配堆栈内存,并将其作为 numpy ndarray 返回。 This question 讨论了一些分配方法,但问题是我不知道如何以编程方式将我的自定义 ctypedef 映射到分配所需的相应 numpy dtype 或 Python 类型代码。

例如:

from cython cimport view
import numpy as np

ctypedef int value_type    # actual type subject to change

# np.empty requires me knowing that Cython int maps to np.int32
def test_return_np_array(size_t N):
    cdef value_type[:] b = np.empty(N, dtype=np.int32)
    b[0]=12                  # from ctypedef int ^
    return np.asarray(b)
# or, Cython memoryview requires the type code 'i'
def test_return_np_array(size_t N):
    cdef value_type[:] b = view.array(shape=(N,), itemsize=sizeof(int), format="i")
    b[0]=12                                                 # from ctypedef int ^
    return np.asarray(b)

我正在使用 typedef,以便可以灵活地更改实际数据类型(例如从 intlong long),而无需修改所有代码。

在纯 Python 中,类型检查很容易:

value_type = int
print(value_type is int)    # True
print(value_type is float)  # False

在 numpy 中,这也可以通过将 dtype 参数化为字符串来轻松实现,例如 value_type="int32" 然后调用 np.empty(N, dtype=value_type)。使用我的 ctypedef,Cython 不会编译 np.empty(N, dtype=value_type),并抱怨“'value_type' 不是常量、变量或函数标识符”。是否有可能在编译时实现这样的目标?

用户不必管理返回的内存,因此malloc 将不是一个选项。

我想出了一个使用 C++ 向量的 hack:<value_type[:N]>vector[value_type](N).data(),但这似乎会导致内存错误。

【问题讨论】:

  • 我的解决方法是手动定义运行时类型和相应的编译时类型,如value_type = np.int32ctypedef np.int32_t value_type_t(可以轻松更改为value_type = np.int64ctypedef np.int64_t value_type_t),基于在/Cython/Includes/numpy/__init__.pxd 中的映射。还提到了here
  • 你说得对,这是最简单的解决方案。我已经根据一些考虑更新了我的答案,这解释了为什么更强大的方法可能值得麻烦。

标签: python c++ c numpy cython


【解决方案1】:

从 C 的角度来看,np.int32 不是一种类型,而是一个 Python 对象,它必须在运行时创建,而不能在编译时创建。

可能最稳健的方法是这个技巧(有关详细信息的解释,请参阅SO-question):

%%cython -a 

import numpy as np

def GET_SIGNED_NUMPY_TYPE():
    cdef int tmp
    return np.asarray(<int[:1]>(&tmp)).dtype

现在

>>> print(GET_SIGNED_NUMPY_TYPE())
int32

优点是,Cython 基础设施用于映射,不需要手动容易出错的工作。


一种不那么神秘但也更容易出错的方法:您可以通过在加载模块时在运行时调用的函数来选择正确的类型:

%%cython
import numpy as np

ctypedef int value_type 

SIGNED_NUMPY_TYPE_MAP = {2 : np.int16, 4 : np.int32, 8 : np.int64}
SIGNED_NUMPY_TYPE = SIGNED_NUMPY_TYPE_MAP[sizeof(value_type)]

def zeros(N):
    return np.zeros(N, dtype=SIGNED_NUMPY_TYPE)

现在:

>>> print(zeros(1).dtype)
int32

int 更改为long long 会导致np.int64 被选中。

类似的方法也可以用于内存视图。


正如您所指出的,Cython 教程建议手动映射类型,例如:

ctypedef np.int32_t value_type
SIGNED_NUMPY_TYPE = np.int32

然后根据需要手动更改两者。这个简单的解决方案可能最适合较小的程序和原型。但是,有一些考虑可能需要更稳健的方法:

  • 当两个定义并排放置时,很容易看出它们必须一起更改。对于更复杂的程序,这两个定义可以放在不同的 pxd 或 pyx 文件中,然后这只是时间问题。

  • 只要使用固定大小的类型(int32,int64),对应的numpy类型就很明显了。但是对于像 intlong 这样的类型,这并不容易判断:

    • int 只保证至少有 2 个字节,并且不超过 long。编译器可以决定选择哪个大小,可能有点担心没有保证,但是通常的嫌疑人(gcc、cland、icc 和 msvc)为通常的架构选择 4 个字节。

    • long 已经是一个陷阱:gcc 为 Linux64 选择它为 8 个字节,但在 msvc 中,long 只有 4 个字节长,因此在不知道将使用哪个编译器的情况下,无法在 np.int32 和 @ 之间进行选择987654340@提前。

    • 对于long 的情况,np.int 相当令人困惑,因为人们会期望np.int 映射到int 而不是long!但是在 Linux64/gcc 上,np.int.itemsize 是 8 个字节,而 int 只有 4 个字节长。另一方面,在 Windows64/msvc 上,np.intint 都是 4 个字节。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-27
    • 2015-11-20
    相关资源
    最近更新 更多