【问题标题】:Best way to iterate over a byte/unicode string in Cython在 Cython 中迭代字节/unicode 字符串的最佳方法
【发布时间】:2013-02-26 14:06:46
【问题描述】:

我刚开始使用 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)

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

但它实际上引发了KeyError 并且似乎c 仍然是char(如果s'P' 开头,则缺少的键是80)但是当我print(type(c)) 它时说&lt;class 'str'&gt;

请注意,原始无类型代码在这两个版本下都可以工作,但比 Python 2 上的工作类型版本慢大约两倍。

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

附:如果重要的话,我可以只允许字符串中的 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

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

【问题讨论】:

  • do_stuff is python 函数中几乎没有加速。在这种情况下,您只是输入了循环变量,而不是工作。在 cython 中重写 do_stuff。如果您提供有关 do_stuff 的作用以及 some_dict 值中的内容的信息也会很有帮助。
  • 关于您的 KeyError - 在 C unicode 中通常映射到 int 类型,因此在这种情况下 some_dict 必须是带有 int 键的 C 哈希(或者可能更正确的 Py_UNICODE 类型)。但同样,我敢打赌瓶颈在 do_stuff 中。
  • @TurnaevEvgeny do_stuff 是来自some_dict 的数字的算术运算。基本上,它计算来自some_dict 的值的总和,对应于来自s 的键。我输入了 sum 变量,所以有一些加速。所以问题是如何对循环本身进行cythonize。
  • 对我来说仍然很不清楚。使用示例数据发布更多代码。如果您要将值映射到 255 范围内的任何字符 - 那么只需使用数组而不是 dict。函数应该返回什么?
  • @TurnaevEvgeny 这不是任何字符,只是 ascii 大写的一个子集。但是仍然制作一个(稀疏)数组听起来像是一个理智的想法。返回的值是计算的总和。

标签: python c string unicode cython


【解决方案1】:

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

(未经测试)

【讨论】:

  • 如果你想要哈希,我会建议相对相同。但是使用散列类 Numbers 将具有 C++ 映射(或任何 C 散列)成员。
  • 这是一个非常有趣和有用的例子,谢谢。但是关于检查my_str 是什么的缺失部分几乎就是我最初提出的问题,所以很遗憾我还不能接受答案(尽管你可以指望我+1)。
  • 我刚刚添加了检查,但我当然不认为您最初的问题是关于类型检查的。
  • Python 2.x 和 3.x 的区别在于,str 类型实际上是 2.x 下的 ascii 字节串和 3.x 下的 unicode 字符串,在迭代一个纯 Python 中的 ascii 字符串,但在迁移到 Cython 时让我卡住了。
  • 您检查字符串类型的解决方案似乎是特定于 Python2 的(尽管在概念上很有帮助)。 Python 3 API 有PyBytes_CheckPyUnicode_Check。我可以以某种方式检查其中哪些是定义的吗?也许只是将导入包装在 try/except 中?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多