Помимо того факта, что ваш код должен потерпеть неудачу при цитонизации, потому что вы пытаетесь создать Python-объект (т.е. str(i)
) без gil, ваш код не делает то, что, как вы думаете, должен делать.
Чтобы проанализировать, что происходит, давайте взглянем на простую версию Cython:
%%cython -2
cimport numpy as np
from numpy cimport ndarray as ar
cpdef func_string(ar[np.str, cast=True] x):
print(len(x))
Из вашего сообщения об ошибке можно вычесть, что вы используете Python 3, а расширение Cython построено с (по умолчанию) language_level=2
, поэтому я использую -2
в %%cython
-магическая клетка.
А теперь:
>>> x = np.array(["apple", "pear"], dtype=np.str)
>>> func_string(x)
ValueError: Item size of buffer (20 bytes) does not match size of 'str object' (8 bytes)
Что происходит?
x
это не то, что вы думаете
Сначала давайте взглянем на x
:
>>> x.dtype
<U5
Так что x
не является коллекцией юникод-объектов. Элемент x
состоит из 5 символов юникода, и эти элементы хранятся в памяти последовательно, один за другим. Что важно: та же информация, что и в Unicode-объектах, хранящаяся в другой структуре памяти.
Это одна из странностей numpy, и как работает np.array
: каждый элемент в списке преобразуется в объект Unicode, после чего вычисляется максимальный размер элемента и вычисляется dtype (в данном случае <U5
). и использовал.
np.str
интерпретируется по-разному в коде Cython (ar[np.str] x
) (дважды!)
Первое отличие: в вашем Python3-коде np.str
для unicode
, но в вашем коде Cython, который цифонизирован с language_level=2
, np.str
для bytes
(см. doc ).
Второе отличие: видя np.str
, Cython будет интерпретировать его как массив с объектами Python (возможно, это следует рассматривать как ошибку Cython) - это почти то же самое, как если бы dtype
было np.object
- на самом деле единственное отличие от np.object
- немного отличающиеся сообщения об ошибках.
С помощью этой информации мы можем понять сообщение об ошибке. Во время выполнения проверяется массив ввода (перед выполнением первой строки функции!):
- ожидается, что это массив с python-объектами, то есть 8-байтовые указатели, то есть массив с размером элемента 8 байтов
- получен массив с размером элемента 5 * 4 = 20 байт (один юникод-символ 4 байта)
Таким образом, приведение не может быть выполнено, и выдается наблюдаемое исключение.
вы не можете изменить размер элемента в <U..
-numpy-array :
Теперь давайте посмотрим на следующее:
>>> x = np.array(["apple", b"pear"], dtype=np.str)
>>> x[0] = x[0]+str(0)
>>> x[0]
'apple'
элемент не изменился, потому что строка x[0]+str(0)
была усечена при записи обратно в x
-array: есть место только для 5 символов! Это работало бы (до некоторой степени, пока получающаяся строка не больше чем 5 символов) с "pear"
, хотя:
>>> x[1] = x[1]+str(1)
>>> x[1]
'pear0'
Куда это все тебя приведет?
- Вы, вероятно, хотите использовать
bytes
, а не unicodes
(т.е. dtype=np.bytes_
)
- учитывая, что вы не знаете размер элемента вашего numpy-массива в типе компиляции, вы должны объявить входной массив
x
как ar x
в сигнатуре и развернуть проверки во время выполнения, как это было сделано в Cyber's "dericated" numpy-tutorial .
- если изменения должны быть сделаны на месте, элементы во входном массиве должны быть достаточно большими для результирующих строк.
Все вышеперечисленное не имеет ничего общего с prange
. Чтобы использовать prange
, вы не можете использовать str(i)
, потому что он работает с python-объектами.