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

时间:2013-03-11 11:51:07

标签: python c string unicode cython

我刚刚开始使用Cython,而且对于特定于Google 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)

它在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

这是我唯一能够工作的东西,说实话,它在Python 2上提供了不错的加速,但是在Python 3上打破了。阅读了this一段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

但它实际上会引发KeyErrorc似乎仍为char(如果80s开头,则缺失的密钥为'P' {1}})但当我print(type(c))时,它会显示<class 'str'>

请注意,原始的无类型代码在两个版本下都能正常运行,但比Python 2上的工作类型版本慢了两倍。

那么我如何让它在Python 3上运行呢?然后我如何让它同时在两个Python版本上运行?可以/我应该在类型/版本检查中包装类型声明吗?或者,我是否应该编写两个函数并有条件地将其中一个函数指定为公开名称?

P.S。如果重要的话,我只允许在字符串中使用ASCII字符,但我怀疑它确实如此,因为Cython似乎更喜欢显式编码/解码。


编辑:我也尝试过显式编码并迭代一个bytestring,这很有意义,但是下面的代码:

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

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

1 个答案:

答案 0 :(得分: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;
}

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

(未测试的)