バッファプロトコル—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/c-api/buffer
移動先:案内検索

バッファプロトコル

Pythonで使用可能な特定のオブジェクトは、基になるメモリ配列またはバッファへのアクセスをラップします。 このようなオブジェクトには、組み込みの bytesbytearray 、および array.array のようないくつかの拡張タイプが含まれます。 サードパーティのライブラリは、画像処理や数値解析などの特別な目的のために独自のタイプを定義する場合があります。

これらのタイプにはそれぞれ独自のセマンティクスがありますが、おそらく大きなメモリバッファに支えられているという共通の特徴を共有しています。 その場合、状況によっては、中間コピーなしでそのバッファーに直接アクセスすることが望ましい場合があります。

Pythonは、バッファプロトコルの形式でCレベルでそのような機能を提供します。 このプロトコルには2つの側面があります。

  • プロデューサー側では、タイプは「バッファーインターフェース」をエクスポートできます。これにより、そのタイプのオブジェクトは、基になるバッファーに関する情報を公開できます。 このインターフェイスについては、バッファオブジェクト構造のセクションで説明しています。
  • コンシューマー側では、オブジェクトの生の基になるデータへのポインターを取得するためのいくつかの手段が利用可能です(たとえば、メソッドパラメーター)。

bytesbytearray などの単純なオブジェクトは、基になるバッファーをバイト指向の形式で公開します。 他の形式も可能です。 たとえば、 array.array によって公開される要素はマルチバイト値にすることができます。

バッファインターフェイスのコンシューマの例は、ファイルオブジェクトの write()メソッドです。バッファインターフェイスを介して一連のバイトをエクスポートできる任意のオブジェクトをファイルに書き込むことができます。 write()は、渡されたオブジェクトの内部コンテンツへの読み取り専用アクセスのみを必要としますが、 readinto()などの他のメソッドは、引数のコンテンツへの書き込みアクセスを必要とします。 バッファインターフェイスを使用すると、オブジェクトは読み取り/書き込みバッファと読み取り専用バッファのエクスポートを選択的に許可または拒否できます。

バッファインターフェイスのコンシューマがターゲットオブジェクト上でバッファを取得するには、次の2つの方法があります。

どちらの場合も、バッファが不要になったときに PyBuffer_Release()を呼び出す必要があります。 そうしないと、リソースリークなどのさまざまな問題が発生する可能性があります。

バッファ構造

バッファ構造(または単に「バッファ」)は、別のオブジェクトからのバイナリデータをPythonプログラマに公開する方法として役立ちます。 また、ゼロコピーのスライスメカニズムとしても使用できます。 メモリのブロックを参照する機能を使用すると、Pythonプログラマーに任意のデータを非常に簡単に公開できます。 メモリは、C拡張機能の大きな定数配列、オペレーティングシステムライブラリに渡す前の操作用の生のメモリブロック、またはネイティブのメモリ内形式で構造化データを渡すために使用できます。 。

Pythonインタープリターによって公開されるほとんどのデータ型とは異なり、バッファーは PyObject ポインターではなく、単純なC構造体です。 これにより、非常に簡単に作成およびコピーできます。 バッファの汎用ラッパーが必要な場合は、 memoryview オブジェクトを作成できます。

エクスポートオブジェクトの作成方法の簡単な説明については、バッファオブジェクト構造を参照してください。 バッファの取得については、 PyObject_GetBuffer()を参照してください。

type Py_buffer
void *buf

バッファフィールドによって記述された論理構造の開始へのポインタ。 これは、エクスポータの基になる物理メモリブロック内の任意の場所にすることができます。 たとえば、負の strides の場合、値はメモリブロックの終わりを指している可能性があります。

連続配列の場合、値はメモリブロックの先頭を指します。

void *obj

エクスポートするオブジェクトへの新しい参照。 参照はコンシューマーによって所有され、 PyBuffer_Release()によって自動的にデクリメントされ、NULLに設定されます。 このフィールドは、標準のC-API関数の戻り値に相当します。

特別な場合として、 PyMemoryView_FromBuffer()または PyBuffer_FillInfo()によってラップされる一時バッファーの場合、このフィールドはNULLです。 一般に、オブジェクトのエクスポートではこのスキームを使用してはなりません(MUSTNOT)。

Py_ssize_t len

product(shape) * itemsize。 連続する配列の場合、これは基になるメモリブロックの長さです。 非連続配列の場合、論理構造が連続表現にコピーされた場合の長さです。

((char *)buf)[0] up to ((char *)buf)[len-1]へのアクセスは、隣接性を保証する要求によってバッファーが取得された場合にのみ有効です。 ほとんどの場合、このような要求は PyBUF_SIMPLE または PyBUF_WRITABLE になります。

int readonly

バッファが読み取り専用かどうかのインジケータ。 このフィールドは、 PyBUF_WRITABLE フラグによって制御されます。

Py_ssize_t itemsize

単一要素のバイト単位のアイテムサイズ。 NULL format 以外の値で呼び出される struct.calcsize()の値と同じです。

重要な例外:コンシューマーが PyBUF_FORMAT フラグなしでバッファーを要求した場合、 formatNULLに設定されますが、 itemsize にはまだ値があります元の形式の場合。

shape が存在する場合、等式product(shape) * itemsize == lenは引き続き保持され、コンシューマーは itemsize を使用してバッファーをナビゲートできます。

PyBUF_SIMPLE または PyBUF_WRITABLE リクエストの結果として shapeNULLである場合、コンシューマーは itemsize を無視する必要があります。 itemsize == 1を想定します。

const char *format

NUL で終了する文字列。 struct モジュールスタイルの構文で、単一のアイテムの内容を記述します。 これがNULLの場合、"B"(符号なしバイト)が想定されます。

このフィールドは、 PyBUF_FORMAT フラグによって制御されます。

int ndim

メモリがn次元配列として表す次元の数。 0の場合、 buf はスカラーを表す単一のアイテムを指します。 この場合、 shapestrides 、および suboffsetsNULLでなければなりません。

マクロPyBUF_MAX_NDIMは、次元の最大数を64に制限します。 輸出業者はこの制限を尊重しなければなりません。多次元バッファの消費者は最大PyBUF_MAX_NDIM次元を処理できる必要があります。

Py_ssize_t *shape

長さ ndimPy_ssize_tの配列で、メモリの形状をn次元配列として示します。 shape[0] * ... * shape[ndim-1] * itemsizelen と等しくなければならないことに注意してください。

形状の値はshape[n] >= 0に制限されています。 ケースshape[n] == 0には特別な注意が必要です。 詳細については、複雑な配列を参照してください。

形状配列は、コンシューマーに対して読み取り専用です。

Py_ssize_t *strides

長さ ndimPy_ssize_tの配列で、各次元の新しい要素に到達するためにスキップするバイト数を示します。

ストライド値は任意の整数にすることができます。 通常の配列の場合、ストライドは通常正ですが、コンシューマーはケースstrides[n] <= 0を処理できなければなりません。 詳細については、複雑な配列を参照してください。

ストライド配列は、コンシューマーに対して読み取り専用です。

Py_ssize_t *suboffsets

長さ ndimPy_ssize_tの配列。 suboffsets[n] >= 0の場合、n番目の次元に沿って格納される値はポインターであり、サブオフセット値は、参照解除後に各ポインターに追加するバイト数を示します。 負のサブオフセット値は、逆参照が発生してはならないことを示します(連続したメモリブロックをストライドします)。

すべてのサブオフセットが負の場合(つまり、 参照解除は必要ありません)、このフィールドはNULL(デフォルト値)である必要があります。

このタイプの配列表現は、Python Imaging Library(PIL)によって使用されます。 このような配列の要素にアクセスする方法の詳細については、複雑な配列を参照してください。

サブオフセット配列は、コンシューマーに対して読み取り専用です。

void *internal

これは、エクスポートするオブジェクトが内部で使用するためのものです。 たとえば、これはエクスポータによって整数として再キャストされ、バッファが解放されたときにシェイプ、ストライド、およびサブオフセット配列を解放する必要があるかどうかに関するフラグを格納するために使用される場合があります。 消費者はこの値を変更してはなりません(MUSTNOT)。


バッファリクエストタイプ

バッファは通常、 PyObject_GetBuffer()を介してエクスポートオブジェクトにバッファ要求を送信することによって取得されます。 メモリの論理構造の複雑さは大幅に異なる可能性があるため、コンシューマーは flags 引数を使用して、処理できる正確なバッファータイプを指定します。

すべての Py_buffer フィールドは、リクエストタイプによって明確に定義されています。

リクエストに依存しないフィールド

次のフィールドはフラグの影響を受けず、常に正しい値を入力する必要があります: objbuflen 、[ X154X] itemsize 、 ndim


読み取り専用、フォーマット

PyBUF_WRITABLE
読み取り専用フィールドを制御します。 設定されている場合、エクスポータは書き込み可能なバッファを提供する必要があります。そうでない場合は、失敗を報告します。 それ以外の場合、エクスポーターは読み取り専用または書き込み可能なバッファーを提供できますが、選択はすべてのコンシューマーに対して一貫している必要があります。
PyBUF_FORMAT
format フィールドを制御します。 設定されている場合、このフィールドは正しく入力する必要があります。 それ以外の場合、このフィールドはNULLでなければなりません。


PyBUF_WRITABLE は、次のセクションの任意のフラグに| 'することができます。 PyBUF_SIMPLE は0として定義されているため、 PyBUF_WRITABLE をスタンドアロンフラグとして使用して、単純な書き込み可能なバッファーを要求できます。

PyBUF_FORMAT は、 PyBUF_SIMPLE を除くすべてのフラグに| 'dできます。 後者はすでにフォーマットB(符号なしバイト)を意味します。


形状、ストライド、サブオフセット

メモリの論理構造を制御するフラグは、複雑さの降順でリストされています。 各フラグには、その下にあるフラグのすべてのビットが含まれていることに注意してください。

リクエスト 歩幅 サブオフセット
PyBUF_INDIRECT
はい はい 必要に応じて
PyBUF_STRIDES
はい はい ヌル
PyBUF_ND
はい ヌル ヌル
PyBUF_SIMPLE
ヌル ヌル ヌル


隣接リクエスト

CまたはFortran contiguity は、ストライド情報の有無にかかわらず、明示的に要求できます。 ストライド情報がない場合、バッファはC連続である必要があります。

リクエスト 歩幅 サブオフセット コンティグ
PyBUF_C_CONTIGUOUS
はい はい ヌル C
PyBUF_F_CONTIGUOUS
はい はい ヌル F
PyBUF_ANY_CONTIGUOUS
はい はい ヌル CまたはF
PyBUF_ND はい ヌル ヌル C


複合リクエスト

考えられるすべての要求は、前のセクションのフラグの組み合わせによって完全に定義されます。 便宜上、バッファプロトコルは、頻繁に使用される組み合わせを単一のフラグとして提供します。

次の表で、 U は未定義の隣接を表します。 コンシューマーは、 PyBuffer_IsContiguous()を呼び出して隣接性を判別する必要があります。

リクエスト 歩幅 サブオフセット コンティグ 読み取り専用 フォーマット
PyBUF_FULL
はい はい 必要に応じて U 0 はい
PyBUF_FULL_RO
はい はい 必要に応じて U 1または0 はい
PyBUF_RECORDS
はい はい ヌル U 0 はい
PyBUF_RECORDS_RO
はい はい ヌル U 1または0 はい
PyBUF_STRIDED
はい はい ヌル U 0 ヌル
PyBUF_STRIDED_RO
はい はい ヌル U 1または0 ヌル
PyBUF_CONTIG
はい ヌル ヌル C 0 ヌル
PyBUF_CONTIG_RO
はい ヌル ヌル C 1または0 ヌル


複雑な配列

NumPyスタイル:形状と歩幅

NumPyスタイルの配列の論理構造は、 itemsizendimshape 、および strides によって定義されます。

ndim == 0の場合、 buf が指すメモリ位置は、サイズ itemsize のスカラーとして解釈されます。 その場合、 shapestrides はどちらもNULLです。

stridesNULLの場合、配列は標準のn次元C配列として解釈されます。 それ以外の場合、コンシューマーは次のようにn次元配列にアクセスする必要があります。

ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);

上記のように、 buf は、実際のメモリブロック内の任意の場所を指すことができます。 エクスポータは、次の関数を使用してバッファの有効性を確認できます。

def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
    """Verify that the parameters represent a valid array within
       the bounds of the allocated memory:
           char *mem: start of the physical memory block
           memlen: length of the physical memory block
           offset: (char *)buf - mem
    """
    if offset % itemsize:
        return False
    if offset < 0 or offset+itemsize > memlen:
        return False
    if any(v % itemsize for v in strides):
        return False

    if ndim <= 0:
        return ndim == 0 and not shape and not strides
    if 0 in shape:
        return True

    imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
               if strides[j] <= 0)
    imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
               if strides[j] > 0)

    return 0 <= offset+imin and offset+imax+itemsize <= memlen

PILスタイル:形状、ストライド、サブオフセット

通常の項目に加えて、PILスタイルの配列には、次元内の次の要素に到達するために従わなければならないポインターを含めることができます。 たとえば、通常の3次元C配列char v[2][2][3]は、2つの2次元配列char (*v[2])[2][3]への2つのポインターの配列として表示することもできます。 サブオフセット表現では、これらの2つのポインターは、 buf の先頭に埋め込むことができ、メモリ内の任意の場所に配置できる2つのchar x[2][3]配列を指します。

NULLストライドとサブオフセットの両方がある場合に、N次元インデックスが指すND配列内の要素へのポインターを返す関数を次に示します。

void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
                       Py_ssize_t *suboffsets, Py_ssize_t *indices) {
    char *pointer = (char*)buf;
    int i;
    for (i = 0; i < ndim; i++) {
        pointer += strides[i] * indices[i];
        if (suboffsets[i] >=0 ) {
            pointer = *((char**)pointer) + suboffsets[i];
        }
    }
    return (void*)pointer;
}