在 Cython 中迭代字节/Unicode 字符串的最佳方法

Lev*_*sky 5 c python string unicode cython

我刚刚开始使用 Cython,结果也很难用谷歌搜索 Cython 特定的东西,所以提前抱歉。

我正在用 Cython 重新实现一个 Python 函数。在 Python 中它几乎是这样的:

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

它在 Python 2 和 3 上运行良好。但是如果我尝试输入sc,它会在至少一个 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
Run Code Online (Sandbox Code Playgroud)

老实说,这是我唯一可以工作的事情,它在 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
Run Code Online (Sandbox Code Playgroud)

但它实际上引发了 aKeyError并且似乎c仍然是 a char(缺少的键是80ifs以 a 开头'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
Run Code Online (Sandbox Code Playgroud)

比我在 Python 2 上的第一次尝试慢 3 倍(接近纯 Python 函数的速度),在 Python 3 上几乎慢了 2 倍。

Tur*_*eny 0

foo.h

// #include <unistd.h>;  // for ssize_t
double foo(char * str, ssize_t str_len, double weights[256]){
    double output = 0.0;
    int i;
    for(i = 0; i < str_len; ++i){
        output += weights[str[i]];
    }
    return output;
}
Run Code Online (Sandbox Code Playgroud)
from cpython.string cimport PyString_GET_SIZE, PyString_Check, PyString_AS_STRING

cdef extern from "foo.h":
    double foo(char * str, ssize_t str_len, double weights[256])   

cdef class Numbers:
    cdef double nums[256]

    def __cinit__(self, py_numbers):
        for x in range(256):
            self.nums[i] = py_numbers[i]

def py_foo(my_str, Numbers nums_inst):
    cdef:
        double res
    # check here my_str is BYTEstring
    if not PyString_Check(my_str):
        raise TypeError("bytestring expected got %s instead" % type(my_str))
    res = foo(PyString_AS_STRING(my_str), PyString_GET_SIZE(my_str), nums_inst.nums)
    return res
Run Code Online (Sandbox Code Playgroud)

(未经测试)