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, но в msvclong
длиной всего 4 байта, поэтому, не зная, какой компилятор будет использоваться, нельзя выбирать междуnp.int32
а такжеnp.int64
заблаговременно.Для случая
long
естьnp.int
что довольно запутанно, потому что можно было бы ожидатьnp.int
сопоставить сint
и неlong
! Однако на Linux64/ GCCnp.int.itemsize
8 байт, ноint
длиной всего 4 байта. С другой стороны на Windows64/ MSVC обаnp.int
а такжеint
4 байта.