14

如何编写一个 Cython 函数,它将字节字符串对象(普通字符串、字节数组或其他遵循缓冲区协议的对象)作为类型化的 memoryview

根据Unicode 和传递字符串Cython 教程页面,以下应该可以工作:

cpdef object printbuf(unsigned char[:] buf):
    chars = [chr(x) for x in buf]
    print repr(''.join(chars))

它适用于字节数组和其他可写缓冲区:

$ python -c 'import test; test.printbuf(bytearray("test\0ing"))'
'test\x00ing'

但它不适用于普通字符串和其他只读缓冲区对象:

$ python -c 'import test; test.printbuf("test\0ing")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "test.pyx", line 1, in test.printbuf (test.c:1417)
  File "stringsource", line 614, in View.MemoryView.memoryview_cwrapper (test.c:6795)
  File "stringsource", line 321, in View.MemoryView.memoryview.__cinit__ (test.c:3341)
BufferError: Object is not writable.

查看生成的 C 代码,Cython 总是将PyBUF_WRITABLE标志传递给PyObject_GetBuffer(),这解释了异常。

我可以自己手动查看缓冲区对象,但这并不方便:

from cpython.buffer cimport \
    PyBUF_SIMPLE, PyBUF_WRITABLE, \
    PyObject_CheckBuffer, PyObject_GetBuffer, PyBuffer_Release

cpdef object printbuf(object buf):
    if not PyObject_CheckBuffer(buf):
        raise TypeError("argument must follow the buffer protocol")
    cdef Py_buffer view
    PyObject_GetBuffer(buf, &view, PyBUF_SIMPLE)
    try:
        chars = [chr((<unsigned char *>view.buf)[i])
                 for i in range(view.len)]
        print repr(''.join(chars))
    finally:
        PyBuffer_Release(&view)
$ python -c 'import test; test.printbuf(bytearray("test\0ing"))'
'test\x00ing'
$ python -c 'import test; test.printbuf("test\0ing")'
'test\x00ing'

我做错了什么,还是 Cython 不支持将只读缓冲区对象(例如普通字符串)强制转换为类型化的 memoryview 对象?

4

2 回答 2

21

尽管文档另有说明,但 Cython(至少到 0.22 版)不支持将只读缓冲区对象强制转换为类型化的 memoryview 对象。Cython 总是将PyBUF_WRITABLE标志传递给 PyObject_GetBuffer(),即使它不需要写访问权限。这会导致只读缓冲区对象引发异常。

在 Cython 开发者邮件列表中提出了这个问题,甚至包括了一个(非常粗糙的)补丁。我从来没有得到回复,所以我假设 Cython 开发人员对修复这个错误不感兴趣。

于 2015-03-15T23:00:44.943 回答
9

此问题已在 Cython 0.28 中修复,发布于 2018 年 3 月 13 日(PR #1869)。变更日志说:

const 修饰符可以应用于 memoryview 声明以允许只读缓冲区作为输入。

文档中还有一个新部分

如果您像这样编写函数,您给出的示例将在 Cython 0.28 中工作:

cpdef object printbuf(const unsigned char[:] buf):
    chars = [chr(x) for x in buf]
    print repr(''.join(chars))
于 2018-06-21T11:55:12.133 回答