Cython: как получить "фактический тип Python" (type code/dtype) из типа C-level

Я хотел бы выделить память стека для вида памяти, определенного с помощью ctypedef, и вернуть его как numpy ndarray. В этом вопросе обсуждались несколько методов размещения, но суть в том, что я не знаю, как программно отобразить мой пользовательский 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, чтобы я мог гибко изменить фактический тип данных (скажем, из int в long 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++: <value_type[:N]>vector[value_type](N).data(), но это, кажется, вызывает ошибки памяти.

1 ответ

С точки зрения С np.int32 это не тип, а объект Python, который должен быть создан во время выполнения и не может быть создан во время компиляции. Таким образом, вы можете выбрать правильный тип через функцию, которая вызывается во время выполнения, когда модуль загружен:

%%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 очевиден. Однако для таких типов, как int а также long это не легко сказать:

    • int гарантированно иметь не менее 2 и не более байтов, чем long, Компилятор может решить, какой размер будет выбран. Возможно, он немного обеспокоен отсутствием гарантии, однако обычные подозреваемые (gcc, cland, icc и msvc) выбирают 4 байта для обычных архитектур.

    • long ловушка уже есть: gcc выбирает 8 байт для Linux64, но в msvc long длиной всего 4 байта, поэтому, не зная, какой компилятор будет использоваться, нельзя выбирать между np.int32 а также np.int64 заблаговременно.

    • Для случая long есть np.int что довольно запутанно, потому что можно было бы ожидать np.int сопоставить с int и не long! Однако на Linux64/ GCC np.int.itemsize 8 байт, но int длиной всего 4 байта. С другой стороны на Windows64/ MSVC оба np.int а также int 4 байта.

Другие вопросы по тегам