14

How can I write a Cython function that takes a byte string object (a normal string, a bytearray, or another object that follows the buffer protocol) as a typed memoryview?

According to the Unicode and Passing Strings Cython tutorial page, the following should work:

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

It does work for bytearrays and other writable buffers:

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

But it doesn't work for normal strings and other read-only buffer objects:

$ 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.

Looking at the generated C code, Cython is always passing the PyBUF_WRITABLE flag to PyObject_GetBuffer(), which explains the exception.

I can manually get a view into the buffer object myself, but it's not as convenient:

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'

Am I doing something wrong, or does Cython not support coercing read-only buffer objects (such as normal strings) into typed memoryview objects?

Richard Hansen
  • 51,690
  • 20
  • 90
  • 97
  • I found your patch [here](https://mail.python.org/pipermail/cython-devel/2015-February/004316.html) even adding `const ` does not help, so this means the suggested documentation is not working. – dashesy Mar 15 '15 at 21:42
  • `const` now works for me with Cython 0.28.4 – winni2k Jul 30 '18 at 12:33

2 Answers2

21

Despite the documentation suggesting otherwise, Cython (at least up to version 0.22) does not support coercing read-only buffer objects into typed memoryview objects. Cython always passes the PyBUF_WRITABLE flag to PyObject_GetBuffer(), even when it doesn't need write access. This causes read-only buffer objects to raise an exception.

I raised this issue on the Cython developer mailing list, and even included a (very rough) patch. I never got a reply, so I assume the Cython developers are not interested in fixing this bug.

Richard Hansen
  • 51,690
  • 20
  • 90
  • 97
  • I got the same problem too, why they're not interested in fixing this? – avocado Dec 09 '15 at 00:49
  • You might try submitting the patch as a GitHub pull request; and adding in some test cases. – joeln Apr 21 '16 at 04:36
  • Indeed, have you tried to push the patch through GitHub? – K3---rnc Feb 14 '17 at 13:46
  • To repeat what others said, this likely means they did not see or understand the patch, or did not have capacity for its rough state -- _not_ that they are not interested in fixing a bug. It's normal human behavior to reply when there is capacity to do so, even to things you're not interested in. – fuzzyTew Jun 05 '22 at 10:27
10

This issue was fixed in Cython 0.28, released 2018-03-13 (PR #1869). The changelog says:

The const modifier can be applied to memoryview declarations to allow read-only buffers as input.

There is also a new section in the documentation.

The example you gave will work in Cython 0.28 if you write your function like this:

cpdef object printbuf(const unsigned char[:] buf):
    chars = [chr(x) for x in buf]
    print repr(''.join(chars))
Marcel M
  • 1,244
  • 12
  • 19