8

Cython で Python 2.7 拡張モジュールを作成しています。 C ライブラリから提供されたメモリのチャンクをラップする新しいスタイルのバッファ インターフェイスを実装する Python オブジェクトを作成するにはどうすればよいですか? メモリのチャンクは単なるバイト文字列であり、構造体や多次元配列ではありません。ポインターと長さ、およびポインターの有効期間に関する詳細が与えられconst void *ます。

メモリをコピーできません。アプリケーションのパフォーマンスが低下します。

古いスタイルのバッファ オブジェクトでは単純に を使用できPyBuffer_FromMemory()ましたが、新しいスタイルのバッファ オブジェクトを生成する同様に簡単な方法が見つからないようです。

バッファ インターフェイスを実装する独自のクラスを作成する必要がありますか? または、Cython はこれを行う簡単な方法を提供していますか?

Cython のドキュメントからUnicode と Passing StringsTyped 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\0 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
4

3 に答える 3

5

および特殊メソッドを定義することにより、バッファ プロトコルを実装する拡張型を定義できます。例えば:__getbuffer____releasebuffer__

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\0 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'

もっと簡単な方法があるかどうかはわかりません。

于 2015-01-27T08:42:33.547 に答える
4

Python 3.3 には、提供された C バッファから Python オブジェクトPyMemoryView_FromMemoryを作成する C-API 関数があります。オブジェクトは確かに新しいスタイルのバッファ インターフェイスを実装しています。memoryviewmemoryview

そのソースを調べると、かなり単純であることがわかります。PyMemoryView_FromBuffer前者がそれ自体で満たされることを除いて、それは同じことをPy_buffer行いPyBuffer_FillInfoます。

後者は 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\0 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

ただし、返される値には次のような repr があることに注意してください<MemoryView of 'memoryview' at 0x7f216fc70ad0>。これは、Cython がmemoryview内部でむき出しになっているように見えるため_memoryviewsliceです。オブジェクトは既にバッファ インターフェイスを実装しているため、代わりに単に呼び出しmemoryviewの結果を返す必要があります。PyMemoryView_FromBuffer

さらに、バッファの有効期間を管理する責任があります。memoryviewこの方法で作成されたオブジェクトは、メモリを自動的に解放しません。自分で行う必要がありますmemorybuffer。参照がない場合にのみ行うようにしてください。この点で、Richard Hansen による回答は、はるかに優れた代替手段です。

于 2016-08-09T23:40:05.733 に答える
3

@RichardHansenが彼の自己回答で正しく観察しているように、必要なのはバッファプロトコルを実装し、メモリを管理する適切なデストラクタを持つクラスです。

実際、Cython はかなり軽量なクラスを の形式で組み込みで提供しているcython.view.arrayため、独自のクラスを作成する必要はありません。リンクしたページに実際に文書化されていますが、ケースに合った簡単な例を提供するために:

# 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が に設定されています。設定により、標準ライブラリ関数が使用されます。Falsecstrcallback_free_datafree

于 2020-04-01T16:59:51.920 に答える