如何获取ctypes指针的数据的格式字符串

时间:2019-01-28 10:28:24

标签: python-3.x ctypes python-c-api

给出一个ctypes指针,例如double**

import ctypes
data=(ctypes.POINTER(ctypes.c_double)*4)()   #  results in [NULL, NULL, NULL, NULL]

是否可以获得描述data的内存布局的format string

现在,我创建一个memoryview来获取此信息,这听起来有些愚蠢:

view=memoryview(data)
print(view.format)   # prints: &<d

是否有更直接的方式,开销更少?也许通过使用C-API?


如果有帮助的话,可以用有意义的值填充data

import ctypes
data=(ctypes.POINTER(ctypes.c_double)*2)(
             (ctypes.c_double*2)(1.0,2.0), 
             (ctypes.c_double*1)(3.0))  

#  results in [ 
#               ptr0 -> [1,2],
#               ptr1 -> [3]
#             ]   
print(data[1][0])  #  prints 3.0      

1 个答案:

答案 0 :(得分:0)

似乎没有什么比memoryview(data).format更好的了。但是,可以使用C-API加快这一步。

格式字符串(如in PEP3118所述扩展了结构格式字符串语法)是递归计算的,并存储在format-member of the StgDictObject-object中,可以在tp_dict中找到-现场  ctypes-arrays / pointers:

typedef struct {
    PyDictObject dict;          /* first part identical to PyDictObject */
    ...
    /* pep3118 fields, pointers neeed PyMem_Free */
    char *format;
    int ndim;
    Py_ssize_t *shape;
    ...
} StgDictObject;

仅在递归计算期间和a buffer is exported时访问此format字段-这就是memoryview获取此信息的方式:

static int PyCData_NewGetBuffer(PyObject *myself, Py_buffer *view, int flags)
{
    ...
    /* use default format character if not set */
    view->format = dict->format ? dict->format : "B";
    ...
    return 0;
}

现在,我们可以使用C-API填充缓冲区(无需创建实际的memoryview),此处使用Python实现:

%%cython

from cpython cimport buffer

def get_format_via_buffer(obj):
    cdef buffer.Py_buffer view
    buffer.PyObject_GetBuffer(obj, &view, buffer.PyBUF_FORMAT|buffer.PyBUF_ANY_CONTIGUOUS)
    cdef bytes format = view.format
    buffer.PyBuffer_Release(&view)
    return format

此版本比通过memoryview快3倍:

import ctypes
c=(ctypes.c_int*3)()

%timeit get_format_via_buffer(c)   #  295 ns ± 10.3 
%timeit memoryview(c).format       #  936 ns ± 7.43 ns 

在我的机器上,调用def函数大约需要160 ns的开销,而创建字节对象大约需要50毫秒。


即使由于不可避免的开销而对它进行进一步优化没有太大意义,但至少在理论上仍对如何提高它有兴趣。

如果一个人真的也想节省填写Py_buffer-struct的成本,那么没有干净的方法:ctypes-module不是Python-C-API的一部分(它不在include-目录中) ),因此前进的方式是重复the solution Cython uses with the array.array,即对对象的内存布局进行硬编码(这会使该解决方案变得脆弱,因为the memory-layout of StgDictObject可能会不同步)。

在这里使用Cython,并且没有错误检查:

%%cython -a  
from cpython cimport PyObject

# emulate memory-layout (i.e. copy definitions from ctypes.h)
cdef extern from *:
    """
    #include <Python.h>

    typedef struct _ffi_type
    {
      size_t size;
      unsigned short mem[2];
      struct _ffi_type **elements;
    } ffi_type;

    typedef struct {
        PyDictObject dict;          /* first part identical to PyDictObject */

        Py_ssize_t size[3];            /* number of bytes,alignment requirements,number of fields */
        ffi_type ffi_type_pointer;
        PyObject *proto;            /* Only for Pointer/ArrayObject */
        void *setfunc[3];          

        /* Following fields only used by PyCFuncPtrType_Type instances */
        PyObject *argtypes[4];       
        int flags;                  /* calling convention and such */

        /* pep3118 fields, pointers neeed PyMem_Free */
        char *format;
        int ndim;

    } StgDictObject;
    """

    ctypedef struct StgDictObject:
        char *format


def get_format_via_hack(obj):
    cdef PyObject *p =<PyObject *>obj
    cdef StgDictObject *dict = <StgDictObject *>(p.ob_type.tp_dict)
    return dict.format

而且速度很快:

%timeit get_format_via_hack(c) # 243 ns ± 14.5 ns