如何在 Cython 的新型缓冲区对象中包装 C 指针和长度?
How to wrap a C pointer and length in a new-style buffer object in Cython?
我正在用 Cython 编写 Python 2.7 扩展模块。 如何创建一个 Python 对象来实现新式缓冲区接口,该接口包装了 C 库提供给我的一块内存? 这块内存只是一个字符串字节,而不是结构或多维数组。我得到了一个 const void *
指针和一个长度,以及一些关于指针保持有效时间的详细信息。
我无法复制内存——这会降低我的应用程序的性能。
对于旧式缓冲区对象,我可以简单地使用 PyBuffer_FromMemory()
,但我似乎找不到类似的简单方法来生成新式缓冲区对象。
我是否必须创建自己的 class 来实现缓冲区接口?或者 Cython 是否提供了一种简单的方法来做到这一点?
我已经阅读了 Cython 文档中的 Unicode and Passing Strings and Typed Memoryviews 页面,但是该文档不够精确且不是很完整,并且没有与我想要做的看起来相似的示例。
这是我尝试过的 (test.pyx
):
from libc.stdlib cimport malloc
from libc.string cimport memcpy
## pretend that this function is in some C library and that it does
## something interesting. (this function is unrelated to the problem
## I'm experiencing -- this is just an example function that returns a
## chunk of memory that I want to wrap in an object that follows the
## new buffer protocol.)
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cpdef getbuf():
cdef const void *cstr
cdef size_t l
dummy_function(&cstr, &l)
## error: test.pyx:21:20: Invalid base type for memoryview slice: void
#cdef const void[:] ret = cstr[:l]
## error: test.pyx:24:9: Assignment to const 'ret'
#cdef const char[:] ret = cstr[:l]
## error: test.pyx:27:27: Cannot convert 'void const *' to memoryviewslice
#cdef char[:] ret = cstr[:l]
## this next attempt cythonizes, but raises an exception:
## $ python -c 'import test; test.getbuf()'
## Traceback (most recent call last):
## File "<string>", line 1, in <module>
## File "test.pyx", line 15, in test.getbuf (test.c:1411)
## File "test.pyx", line 38, in test.getbuf (test.c:1350)
## File "stringsource", line 614, in View.MemoryView.memoryview_cwrapper (test.c:6763)
## File "stringsource", line 321, in View.MemoryView.memoryview.__cinit__ (test.c:3309)
## BufferError: Object is not writable.
cdef char[:] ret = (<const char *>cstr)[:l]
## this raises the same exception as above
#cdef char[:] ret = (<char *>cstr)[:l]
return ret
您可以定义一个extension type that implements the buffer protocol by defining the __getbuffer__
and __releasebuffer__
special methods。例如:
from cpython.buffer cimport PyBuffer_FillInfo
from libc.stdlib cimport free, malloc
from libc.string cimport memcpy
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cdef void free_dummy_data(const void *p, size_t l, void *arg):
free(<void *>p)
cpdef getbuf():
cdef const void *p
cdef size_t l
dummy_function(&p, &l)
return MemBuf_init(p, l, &free_dummy_data, NULL)
ctypedef void dealloc_callback(const void *p, size_t l, void *arg)
cdef class MemBuf:
cdef const void *p
cdef size_t l
cdef dealloc_callback *dealloc_cb_p
cdef void *dealloc_cb_arg
def __getbuffer__(self, Py_buffer *view, int flags):
PyBuffer_FillInfo(view, self, <void *>self.p, self.l, 1, flags)
def __releasebuffer__(self, Py_buffer *view):
pass
def __dealloc__(self):
if self.dealloc_cb_p != NULL:
self.dealloc_cb_p(self.p, self.l, self.dealloc_cb_arg)
# Call this instead of constructing a MemBuf directly. The __cinit__
# and __init__ methods can only take Python objects, so the real
# constructor is here. See:
# https://mail.python.org/pipermail/cython-devel/2012-June/002734.html
cdef MemBuf MemBuf_init(const void *p, size_t l,
dealloc_callback *dealloc_cb_p,
void *dealloc_cb_arg):
cdef MemBuf ret = MemBuf()
ret.p = p
ret.l = l
ret.dealloc_cb_p = dealloc_cb_p
ret.dealloc_cb_arg = dealloc_cb_arg
return ret
通过上述(命名为 test.pyx
),您会得到以下行为:
$ python -c 'import test; print repr(memoryview(test.getbuf()).tobytes())'
'some test\x00 bytes\x00'
不知道有没有更简单的方法
Python 3.3 有 PyMemoryView_FromMemory
C-API 函数,它从提供的 C 缓冲区创建一个 memoryview
Python 对象。 memoryview
对象确实实现了新式缓冲区接口。
如果您查看 its sources,您会发现它们相当简单。
它与 PyMemoryView_FromBuffer
做同样的事情,除了前者
用 PyBuffer_FillInfo
本身填充 Py_buffer
。
既然Python 2.7中存在后者,为什么我们不能自己调用PyBuffer_FillInfo
呢?
from libc.stdlib cimport malloc
from libc.string cimport memcpy
cdef extern from "Python.h":
ctypedef struct PyObject
object PyMemoryView_FromBuffer(Py_buffer *view)
int PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, int readonly, int infoflags)
enum:
PyBUF_FULL_RO
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cpdef getbuf():
cdef const void *cstr
cdef size_t l
cdef Py_buffer buf_info
cdef char[:] ret
cdef int readonly
dummy_function(&cstr, &l)
readonly = 1
PyBuffer_FillInfo(&buf_info, NULL, <void*>cstr, l, readonly, PyBUF_FULL_RO)
ret = PyMemoryView_FromBuffer(&buf_info)
return ret
但是请注意,returned 值将具有如下所示的 repr:<MemoryView of 'memoryview' at 0x7f216fc70ad0>
。这是因为 Cython 似乎将 memoryview
包裹在 _memoryviewslice
内。由于 memoryview
对象已经实现了缓冲区接口,您可能应该只是 return PyMemoryView_FromBuffer
调用的结果。
此外,您有责任管理缓冲区的生命周期。 memoryview
以这种方式创建的对象不会自动释放内存。你必须自己做,确保只有在没有 memorybuffer
引用它时才这样做。在这方面,Richard Hansen 的回答是更好的选择。
正如@RichardHansen 在他的自我回答中正确观察到的那样,您想要的是 class 实现缓冲区协议,并具有管理内存的合适的析构函数。
Cython 实际上以 cython.view.array
的形式提供了一个相当轻量级的 class 内置其中,因此无需创建您自己的。它实际上是 documented in the page you linked 但为了提供适合您的情况的快速示例:
# at the top of your file
from cython.view cimport array
# ...
# after the call to dummy_function
my_array = array(shape=(l,), itemsize=sizeof(char), format='b', # or capital B depending on if it's signed
allocate_buffer=False)
my_array.data = cstr
my_array.callback_free_data = free
cdef char[:] ret = my_array
请注意几个位:allocate_buffer
设置为 False
,因为您要在 cstr
中分配自己的空间。设置 callback_free_data
确保使用标准库 free
函数。
我正在用 Cython 编写 Python 2.7 扩展模块。 如何创建一个 Python 对象来实现新式缓冲区接口,该接口包装了 C 库提供给我的一块内存? 这块内存只是一个字符串字节,而不是结构或多维数组。我得到了一个 const void *
指针和一个长度,以及一些关于指针保持有效时间的详细信息。
我无法复制内存——这会降低我的应用程序的性能。
对于旧式缓冲区对象,我可以简单地使用 PyBuffer_FromMemory()
,但我似乎找不到类似的简单方法来生成新式缓冲区对象。
我是否必须创建自己的 class 来实现缓冲区接口?或者 Cython 是否提供了一种简单的方法来做到这一点?
我已经阅读了 Cython 文档中的 Unicode and Passing Strings and Typed Memoryviews 页面,但是该文档不够精确且不是很完整,并且没有与我想要做的看起来相似的示例。
这是我尝试过的 (test.pyx
):
from libc.stdlib cimport malloc
from libc.string cimport memcpy
## pretend that this function is in some C library and that it does
## something interesting. (this function is unrelated to the problem
## I'm experiencing -- this is just an example function that returns a
## chunk of memory that I want to wrap in an object that follows the
## new buffer protocol.)
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cpdef getbuf():
cdef const void *cstr
cdef size_t l
dummy_function(&cstr, &l)
## error: test.pyx:21:20: Invalid base type for memoryview slice: void
#cdef const void[:] ret = cstr[:l]
## error: test.pyx:24:9: Assignment to const 'ret'
#cdef const char[:] ret = cstr[:l]
## error: test.pyx:27:27: Cannot convert 'void const *' to memoryviewslice
#cdef char[:] ret = cstr[:l]
## this next attempt cythonizes, but raises an exception:
## $ python -c 'import test; test.getbuf()'
## Traceback (most recent call last):
## File "<string>", line 1, in <module>
## File "test.pyx", line 15, in test.getbuf (test.c:1411)
## File "test.pyx", line 38, in test.getbuf (test.c:1350)
## File "stringsource", line 614, in View.MemoryView.memoryview_cwrapper (test.c:6763)
## File "stringsource", line 321, in View.MemoryView.memoryview.__cinit__ (test.c:3309)
## BufferError: Object is not writable.
cdef char[:] ret = (<const char *>cstr)[:l]
## this raises the same exception as above
#cdef char[:] ret = (<char *>cstr)[:l]
return ret
您可以定义一个extension type that implements the buffer protocol by defining the __getbuffer__
and __releasebuffer__
special methods。例如:
from cpython.buffer cimport PyBuffer_FillInfo
from libc.stdlib cimport free, malloc
from libc.string cimport memcpy
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cdef void free_dummy_data(const void *p, size_t l, void *arg):
free(<void *>p)
cpdef getbuf():
cdef const void *p
cdef size_t l
dummy_function(&p, &l)
return MemBuf_init(p, l, &free_dummy_data, NULL)
ctypedef void dealloc_callback(const void *p, size_t l, void *arg)
cdef class MemBuf:
cdef const void *p
cdef size_t l
cdef dealloc_callback *dealloc_cb_p
cdef void *dealloc_cb_arg
def __getbuffer__(self, Py_buffer *view, int flags):
PyBuffer_FillInfo(view, self, <void *>self.p, self.l, 1, flags)
def __releasebuffer__(self, Py_buffer *view):
pass
def __dealloc__(self):
if self.dealloc_cb_p != NULL:
self.dealloc_cb_p(self.p, self.l, self.dealloc_cb_arg)
# Call this instead of constructing a MemBuf directly. The __cinit__
# and __init__ methods can only take Python objects, so the real
# constructor is here. See:
# https://mail.python.org/pipermail/cython-devel/2012-June/002734.html
cdef MemBuf MemBuf_init(const void *p, size_t l,
dealloc_callback *dealloc_cb_p,
void *dealloc_cb_arg):
cdef MemBuf ret = MemBuf()
ret.p = p
ret.l = l
ret.dealloc_cb_p = dealloc_cb_p
ret.dealloc_cb_arg = dealloc_cb_arg
return ret
通过上述(命名为 test.pyx
),您会得到以下行为:
$ python -c 'import test; print repr(memoryview(test.getbuf()).tobytes())'
'some test\x00 bytes\x00'
不知道有没有更简单的方法
Python 3.3 有 PyMemoryView_FromMemory
C-API 函数,它从提供的 C 缓冲区创建一个 memoryview
Python 对象。 memoryview
对象确实实现了新式缓冲区接口。
如果您查看 its sources,您会发现它们相当简单。
它与 PyMemoryView_FromBuffer
做同样的事情,除了前者
用 PyBuffer_FillInfo
本身填充 Py_buffer
。
既然Python 2.7中存在后者,为什么我们不能自己调用PyBuffer_FillInfo
呢?
from libc.stdlib cimport malloc
from libc.string cimport memcpy
cdef extern from "Python.h":
ctypedef struct PyObject
object PyMemoryView_FromBuffer(Py_buffer *view)
int PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, int readonly, int infoflags)
enum:
PyBUF_FULL_RO
cdef void dummy_function(const void **p, size_t *l):
cdef void *tmp = malloc(17)
memcpy(tmp, "some test[=10=] bytes", 17)
p[0] = tmp
l[0] = 17
cpdef getbuf():
cdef const void *cstr
cdef size_t l
cdef Py_buffer buf_info
cdef char[:] ret
cdef int readonly
dummy_function(&cstr, &l)
readonly = 1
PyBuffer_FillInfo(&buf_info, NULL, <void*>cstr, l, readonly, PyBUF_FULL_RO)
ret = PyMemoryView_FromBuffer(&buf_info)
return ret
但是请注意,returned 值将具有如下所示的 repr:<MemoryView of 'memoryview' at 0x7f216fc70ad0>
。这是因为 Cython 似乎将 memoryview
包裹在 _memoryviewslice
内。由于 memoryview
对象已经实现了缓冲区接口,您可能应该只是 return PyMemoryView_FromBuffer
调用的结果。
此外,您有责任管理缓冲区的生命周期。 memoryview
以这种方式创建的对象不会自动释放内存。你必须自己做,确保只有在没有 memorybuffer
引用它时才这样做。在这方面,Richard Hansen 的回答是更好的选择。
正如@RichardHansen 在他的自我回答中正确观察到的那样,您想要的是 class 实现缓冲区协议,并具有管理内存的合适的析构函数。
Cython 实际上以 cython.view.array
的形式提供了一个相当轻量级的 class 内置其中,因此无需创建您自己的。它实际上是 documented in the page you linked 但为了提供适合您的情况的快速示例:
# at the top of your file
from cython.view cimport array
# ...
# after the call to dummy_function
my_array = array(shape=(l,), itemsize=sizeof(char), format='b', # or capital B depending on if it's signed
allocate_buffer=False)
my_array.data = cstr
my_array.callback_free_data = free
cdef char[:] ret = my_array
请注意几个位:allocate_buffer
设置为 False
,因为您要在 cstr
中分配自己的空间。设置 callback_free_data
确保使用标准库 free
函数。