首页 > 解决方案 > 如何获取 ctypes-pointer 数据的格式字符串

问题描述

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

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

是否可以获得描述内存布局的格式字符串data

现在,我创建了一个 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      

标签: python-3.xctypespython-c-api

解决方案


似乎没有什么从根本上比memoryview(data).format. 但是,可以通过使用 C-API 来加快这一进程。

格式字符串(扩展了结构格式字符串语法,如PEP3118 中所述)是递归计算的,并存储在-objectformatStgDictObject-member中,可以在tp_dictctypes-arrays/pointers 的 -field 中找到:

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

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

这个版本比 via 快大约 3 倍memoryview

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-function大约需要 160 ns 开销,50创建字节对象大约需要 ms。


即使由于不可避免的开销而进一步优化它没有多大意义,但至少在理论上仍然存在如何加速它的兴趣。

如果一个人真的想削减填充 Py_buffer-struct 的成本,那么没有干净的方法:ctypes-module 不是 Python-C-API 的一部分(它不在包含目录中),所以前进的方法是重复Cython 使用的解决方案array.array,即硬编码对象的内存布局(这使得该解决方案变得脆弱,因为内存布局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 

推荐阅读