Лучший способ перебора строки байта / юникода в Cython

Я только начинаю с Cython, и мне тоже очень сложно разобраться в Google Cython, поэтому извините заранее.

Я повторно реализую функцию Python с Cython. Это довольно похоже на Python:

def func(s, numbers=None): if numbers: some_dict = numbers else: some_dict = default return sum(some_dict[c] for c in s) 

И он отлично работает на Python 2 и 3. Но если я попытаюсь ввести s и c , он разбивает хотя бы одну из версий Python. Я пытался:

 def func(char *s, numbers=None): if numbers: some_dict = numbers else: some_dict = default cdef char c cdef double m = 0.0 for c in s: m += some_dict[<bytes>c] return m 

Это, по правде говоря, единственное, что я получил, чтобы работать, и это дает достойное ускорение на Python 2, но ломается на Python 3. Прочитав этот кусок документов Cython, я подумал, что на Python 3 будет работать следующее:

 def func(unicode s, numbers=None): if numbers: some_dict = numbers else: some_dict = default cdef double m = 0.0 for c in s: m += some_dict[c] return m 

но на самом деле он вызывает KeyError и кажется, что c по-прежнему является KeyError char (отсутствующий ключ равен 80 если s начинается с 'P' ), но когда я print(type(c)) он говорит <class 'str'> .

Обратите внимание, что исходный нетипизированный код работает в обеих версиях, но примерно вдвое медленнее, чем рабочая типизированная версия на Python 2.

Итак, как мне заставить его работать на Python 3 вообще, а затем как мне заставить его работать на обеих версиях Python сразу? Может / должен ли я обматывать объявления типа в проверках типа / версии? Или я могу написать две функции и условно присвоить одно из них общедоступному имени?

PS У меня все в порядке, если это имеет значение только для символов ASCII в строке, но я сомневаюсь, что это так, поскольку Cython, похоже, предпочитает явное кодирование / декодирование.


Изменить: я также пробовал явное кодирование и итерацию по байтовой строке, что имело бы смысл, но следующий код:

 def func(s, numbers=None): if numbers: some_dict = numbers else: some_dict = default cdef double m = 0.0 cdef bytes bs = s.encode('ascii') cdef char c for c in bs: m += some_dict[(<bytes>c).decode('ascii')] return m 

в 3 раза медленнее, чем моя первая попытка на Python 2 (близкая к скорости чистой функции Python) и почти на 2 раза медленнее на Python 3.

foo.h

 // #include <unistd.h> // для ssize_t
 double foo (char * str, ssize_t str_len, двойные веса [256]) {
     двойной выход = 0,0;
     int i;
     для (i = 0; i <str_len; ++ i) {
         output + = веса [str [i]];
     }
     обратный выход;
 }

от cpython.string cimport PyString_GET_SIZE, PyString_Check, PyString_AS_STRING

cdef extern из "foo.h":
double foo (char * str, ssize_t str_len, двойные веса [256])

cdef class Numbers:
CDEF:
double nums [256]
def _ cinit _ (self, py_numbers):
для x в диапазоне (256):
self.nums [i] = py_numbers [i]

def py_foo (my_str, Numbers nums_inst):
CDEF:
двойной res
# check here my_str is BYTEstring
если не PyString_Check (my_str):
raise TypeError ("bytestring expected got% s вместо"% type (my_str))
res = foo (PyString_AS_STRING (my_str), PyString_GET_SIZE (my_str), nums_inst.nums)
return res
(Непроверенные)