Python 2.7和Python 3.4中使用memoryviews的Ctypes from_buffer

9
我可以帮您翻译成中文。以下是翻译的结果,未做任何修改。

我试图将memoryview中的数据传递到ctypes数组中,在Python 3.4中可以正常工作,但在Python 2.7中无法正常工作。

当我运行以下代码时:

from ctypes import c_byte
data = memoryview(b'012')
array = c_byte * 3
array.from_buffer_copy(data)

在Python 3.4中,我得到了<__main__.c_byte_Array_3 at 0x7f3022cb8730>,但是在Python 2.7.6中,我得到了以下错误信息:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: expected a readable buffer object

这个错误的原因是什么,我该如何使其在两种情况下都起作用?
我知道可以使用以下方法将数据转换为字节:
array.from_buffer_copy(data.tobytes())

但我认为这将生成一份额外的数据副本,不够优雅,因此我正在寻找更好的解决方案(关于tobytes方法是否高效的任何评论也欢迎)。


2
2.x ctypes 调用 PyObject_AsReadBuffer,该函数查找旧的缓冲区协议插槽 bf_getreadbuffer。而 memoryview 类型并没有定义这个插槽,它使用新的 bf_getbuffer 接口。 - Eryk Sun
1个回答

12

这是一个帮助你使用 Python 2 memoryview 对象导出的缓冲区接口创建 ctypes 数组的类。

from ctypes import *

pyapi = PyDLL("PythonAPI", handle=pythonapi._handle)

PyBUF_SIMPLE   = 0
PyBUF_WRITABLE = 0x0001
PyBUF_FORMAT   = 0x0004
PyBUF_ND       = 0x0008
PyBUF_STRIDES  = 0x0010 | PyBUF_ND

PyBUF_C_CONTIGUOUS   = 0x0020 | PyBUF_STRIDES
PyBUF_F_CONTIGUOUS   = 0x0040 | PyBUF_STRIDES
PyBUF_ANY_CONTIGUOUS = 0x0080 | PyBUF_STRIDES
PyBUF_INDIRECT       = 0x0100 | PyBUF_STRIDES

PyBUF_CONTIG_RO  = PyBUF_ND
PyBUF_CONTIG     = PyBUF_ND | PyBUF_WRITABLE

PyBUF_STRIDED_RO = PyBUF_STRIDES
PyBUF_STRIDED    = PyBUF_STRIDES | PyBUF_WRITABLE

PyBUF_RECORDS_RO = PyBUF_STRIDES | PyBUF_FORMAT
PyBUF_RECORDS    = PyBUF_STRIDES | PyBUF_FORMAT | PyBUF_WRITABLE

PyBUF_FULL_RO = PyBUF_INDIRECT | PyBUF_FORMAT
PyBUF_FULL    = PyBUF_INDIRECT | PyBUF_FORMAT | PyBUF_WRITABLE

Py_ssize_t = c_ssize_t
Py_ssize_t_p = POINTER(Py_ssize_t)

class pybuffer(Structure):
    """Python 3 Buffer Interface"""
    _fields_ = (('buf', c_void_p),
                ('obj', c_void_p), # owned reference
                ('len', Py_ssize_t),
                # itemsize is Py_ssize_t so it can be pointed to
                # by strides in the simple case.
                ('itemsize', Py_ssize_t),
                ('readonly', c_int),
                ('ndim', c_int),
                ('format', c_char_p),
                ('shape', Py_ssize_t_p),
                ('strides', Py_ssize_t_p),
                ('suboffsets', Py_ssize_t_p),
                # static store for shape and strides of
                # mono-dimensional buffers.
                ('smalltable', Py_ssize_t * 2),
                ('internal', c_void_p))

    def get_buffer(self, obj=None, flags=PyBUF_SIMPLE):
        self.release_buffer()
        Structure.__init__(self)
        if obj is not None:
            pyapi.PyObject_GetBuffer(obj, byref(self), flags)

    def make_release_buffer():
        import ctypes
        PyBuffer_Release = pyapi.PyBuffer_Release
        memset = ctypes.memset
        byref = ctypes.byref
        sizeof = ctypes.sizeof
        def release_buffer(self):
            if self.obj:
                PyBuffer_Release(byref(self))
                memset(byref(self), 0, sizeof(self))
        return release_buffer

    __init__ = get_buffer
    __del__ = release_buffer = make_release_buffer()
    del make_release_buffer        

    @property
    def as_ctypes(self):
        if self.obj and self.buf:
            arr = (c_char * self.len).from_address(self.buf)
            if self.readonly:
                arr = type(arr).from_buffer_copy(arr)
            else:
                obj = py_object.from_buffer(c_void_p(self.obj)).value
                arr._obj = obj
            return arr


pyapi.PyObject_GetBuffer.argtypes = (py_object,          # obj
                                     POINTER(pybuffer),  # view
                                     c_int)              # flags
pyapi.PyBuffer_Release.argtypes = POINTER(pybuffer),     # view

__all__ = [n for n in list(globals()) if n.startswith('PyBUF')]
__all__.append('pybuffer')

示例:

>>> data = memoryview(b'012')
>>> buf = pybuffer(data)
>>> buf.readonly
1
>>> array = buf.as_ctypes
>>> array[0] = '9'
>>> data[0]
'0'
>>> data = memoryview(bytearray(b'012'))
>>> buf = pybuffer(data)
>>> buf.readonly
0
>>> array = buf.as_ctypes
>>> array[0] = '9'
>>> data[0]
'9'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接