Buffer プロトコル

Pythonで利用可能ないくつかのオブジェクトは、下層にあるメモリ配列または buffer へのアクセスを提供します。このようなオブジェクトとして、組み込みの bytesbytearrayarray.array のようないくつかの拡張型が挙げられます。サードバーティのライブラリは画像処理や数値解析のような特別な目的のために、それら自身の型を定義することができます。

それぞれの型はそれ自身のセマンティクスを持ちますが、おそらく大きなメモリバッファからなるという共通の特徴を共有します。いくつかの状況では仲介するコピーを行うことなく直接バッファにアクセスすることが望まれます。

Pythonは buffer protocol の形式で C レベルの仕組みを提供します。このプロトコルには二つの側面があります:

  • 提供する側では、ある型は、そのオブジェクトの下層にあるバッファに関する情報を提供できる”buffer インタフェース” をエクスポートすることができます。このインタフェースは バッファオブジェクト構造体 (buffer object structure) の節で説明します。

  • 利用する側では、オブジェクトの下層にある生データへのポインタを得るいくつかの手段が利用できます(たとえばメソッド引数)。

bytesbytearray のようなシンプルなオブジェクトは、それらの下層にあるバッファをバイト指向形式で提供します。ほかの形式がありえます: たとえば array.array によって提供される要素は、マルチバイトの値になりえます。

bufferインタフェースの利用者の一例は、ファイルオブジェクトの write() メソッドです: bufferインタフェースを通して一連のバイト列を提供できるどんなオブジェクトでもファイルに書き込むことができます。 write() は、その引数として渡されたオブジェクトの内部要素に対する読み取り専用アクセスのみを必要としますが、 readinto() のような他のメソッドでは、その引数の内容に対する書き込みアクセスが必要です。bufferインタフェースにより、オブジェクトは読み書き両方、読み取り専用バッファへのアクセスを許可するかそれとも拒否するか選択することができます。

bufferインタフェースの利用者には、対象となるオブジェクトのバッファを得る二つの方法があります:

どちらのケースでも、bufferが必要なくなった時に PyBuffer_Release() を呼び出さなければなりません。これを怠ると、リソースリークのような様々な問題につながる恐れがあります。

Buffer 構造体

バッファ構造体(または単純に “buffers”)は別のオブジェクトのバイナリデータをPythonプログラマに提供するのに便利です。これはまた、ゼロコピースライシング機構としても使用できます。このメモリブロックを参照する機能を使うことで、どんなデータでもとても簡単にPythonプログラマに提供することができます。メモリは、C 拡張の大きな配列定数かもしれませんし、オペレーティングシステムライブラリに渡す前のメモリブロックかもしれませんし、構造化データをネイティブのインメモリ形式受け渡すのに使用されるかもしれません。

Pythonインタプリタによって提供される多くのデータ型とは異なり、バッファは PyObject ポインタではなく、シンプルなC 構造体です。そのため、作成とコピーが非常に簡単に行えます。バッファの一般的なラッパーが必要なときは、 memoryview オブジェクトが作成されます。

エクスポートされるオブジェクトを書く方法の短い説明には、 Buffer Object Structures を参照してください。バッファを取得するには、 PyObject_GetBuffer() を参照してください。

Py_buffer
void *buf

バッファフィールドが表している論理構造の先頭を指すポインタ。バッファを提供するオブジェクトの下層物理メモリブロック中のどの位置にもなりえます。たとえば、たとえば strides が負だと、この値はメモリブロックの末尾かもしれません。

contiguous配列では、この値はメモリブロックの先頭を指します。

void *obj

エクスポートされているオブジェクトの新しい参照。参照は消費者によって所有され、PyBuffer_Release() によって自動的にデクリメントされて NULL に設定されます。このフィールドは標準的なC-API 関数の戻り値と等価です。

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

Py_ssize_t len

product(shape) * itemsize 。contiguous配列では、下層のメモリブロックの長さになります。非contiguous 配列では、contiguous表現にコピーされた場合に論理構造がもつ長さです。

((char *)buf)[0] から ((char *)buf)[len-1] の範囲へのアクセスは、連続性(contiguity)を保証する要求によって取得されたバッファに対してのみ許されます。多くのケースではそのような要求は、 PyBUF_SIMPLE または PyBUF_WRITABLE です。

int readonly

バッファが読み取り専用であるか示します。このフィールドは PyBUF_WRITABLE フラグで制御できます。

Py_ssize_t itemsize

要素一つ分のbyte単位のサイズ。 struct.calcsize() を非NULLの format 値に対して呼び出した結果と同じです。

重要な例外: 消費者が PyBUF_FORMAT フラグを設定することなくバッファを要求した場合、 formatNULL に設定されます。しかし itemsize は当初のフォーマットに従った値を保持します。

If shape is present, the equality product(shape) * itemsize == len still holds and the consumer can use itemsize to navigate the buffer.

PyBUF_SIMPLE または PyBUF_WRITABLE で要求した結果、 shapeNULL であれば、消費者は item_size を無視して、 itemsize == 1 と見なさなければなりません。

const char *format

要素一つ分の内容を指定する、 struct モジュールスタイル文法の、 NUL 終端文字列。 このポインタの値が NULL なら、 "B" (符号無しバイト) として扱われます。

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

int ndim

The number of dimensions the memory represents as an n-dimensional array. If it is 0, buf points to a single item representing a scalar. In this case, shape, strides and suboffsets MUST be NULL.

The macro PyBUF_MAX_NDIM limits the maximum number of dimensions to 64. Exporters MUST respect this limit, consumers of multi-dimensional buffers SHOULD be able to handle up to PyBUF_MAX_NDIM dimensions.

Py_ssize_t *shape

An array of Py_ssize_t of length ndim indicating the shape of the memory as an n-dimensional array. Note that shape[0] * ... * shape[ndim-1] * itemsize MUST be equal to len.

Shape values are restricted to shape[n] >= 0. The case shape[n] == 0 requires special attention. See complex arrays for further information.

図形の配列は、使用者にとっては読み取り専用です。

Py_ssize_t *strides

An array of Py_ssize_t of length ndim giving the number of bytes to skip to get to a new element in each dimension.

ストライド値は、任意の整数を指定できます。規定の配列では、ストライドは通常でいけば有効です。しかし利用者は、”strides [n] < = 0”のケースを処理することができる必要があります。詳細については”複雑な配列”を参照してください。

消費者にとって、この strides 配列は読み取り専用です。

Py_ssize_t *suboffsets

An array of Py_ssize_t of length ndim. If suboffsets[n] >= 0, the values stored along the nth dimension are pointers and the suboffset value dictates how many bytes to add to each pointer after de-referencing. A suboffset value that is negative indicates that no de-referencing should occur (striding in a contiguous memory block).

This type of array representation is used by the Python Imaging Library (PIL). See complex arrays for further information how to access elements of such an array.

消費者にとって、suboffsets 配列は読み取り専用です。

void *internal

バッファを提供する側のオブジェクトが内部的に利用するための変数です。例えば、提供側はこの変数に整数型をキャストして、 shape, strides, suboffsets といった配列をバッファを開放するときに同時に解放するべきかどうかを管理するフラグに使うことができるでしょう。バッファを受け取る側は、この値を決して変更してはなりません。

バッファ要求のタイプ

バッファは通常、 PyObject_GetBuffer() を使うことで、エクスポートするオブジェクトにバッファ要求を送ることで得られます。メモリの論理的な構造の複雑性は多岐にわたるため、消費者は flags 引数を使って、自身が扱えるバッファの種類を指定します。

Py_buffer の全フィールドは、要求の種類によって曖昧さを残さずに定義されます。

要求に依存しないフィールド

下記のフィールドは flags の影響を受けずに、常に正しい値で設定されます。: obj, buf, len, itemsize, ndim.

readonly, format

PyBUF_WRITABLE

readonly フィールドを制御します。もしこのフラグが設定されている場合、exporter は、書き込み可能なバッファを提供するか、さもなければ失敗を報告しなければなりません。フラグが設定されていない場合、exporterは、読み込み専用と書き込み可能なバッファのどちらを提供しても構いませんが、どちらで提供するかどうかは全ての消費者に対して一貫性がなければなりません。

PyBUF_FORMAT

format フィールドを制御します。もしフラグが設定されていれば、このフィールドを正しく埋めなければなりません。フラグが設定されていなければ、このフィールドを NULL に設定しなければなりません。

PyBUF_WRITABLE can be |’d to any of the flags in the next section. Since PyBUF_SIMPLE is defined as 0, PyBUF_WRITABLE can be used as a stand-alone flag to request a simple writable buffer.

PyBUF_FORMAT は、PyBUF_SIMPLE 以外のフラグと一緒に設定できます。後者のフラグは B (符号なしバイト)フォーマットを非明示的に指定しています。

shape, strides, suboffsets

このフラグは、以下で複雑性が大きい順に並べたメモリの論理的な構造を制御します。個々のフラグは、それより下に記載されたフラグのすべてのビットを含むことに注意してください。

Request shape strides suboffsets
PyBUF_INDIRECT
yes yes if needed
PyBUF_STRIDES
yes yes NULL
PyBUF_ND
yes NULL NULL
PyBUF_SIMPLE
NULL NULL NULL

contiguity requests

ストライドの情報があってもなくても、C または Fortran の連続性がはっきりと要求される可能性があります。ストライド情報なしに、バッファーは C と隣接している必要があります。

Request shape strides suboffsets contig
PyBUF_C_CONTIGUOUS
yes yes NULL C
PyBUF_F_CONTIGUOUS
yes yes NULL F
PyBUF_ANY_CONTIGUOUS
yes yes NULL

C か F

PyBUF_ND
yes NULL NULL C

compound requests

可能性のあるすべての要求は、完全にいくつか前のセクションでのフラグの組み合わせによって定義されます。便宜上、バッファー プロトコルは 1 つのフラグとして頻繁に使用される組み合わせを提供します。

In the following table U stands for undefined contiguity. The consumer would have to call PyBuffer_IsContiguous() to determine contiguity.

Request shape strides suboffsets contig readonly format
PyBUF_FULL
yes yes if needed U 0 yes
PyBUF_FULL_RO
yes yes if needed U

1 か 0

yes
PyBUF_RECORDS
yes yes NULL U 0 yes
PyBUF_RECORDS_RO
yes yes NULL U

1 か 0

yes
PyBUF_STRIDED
yes yes NULL U 0 NULL
PyBUF_STRIDED_RO
yes yes NULL U

1 か 0

NULL
PyBUF_CONTIG
yes NULL NULL C 0 NULL
PyBUF_CONTIG_RO
yes NULL NULL C

1 か 0

NULL

Complex arrays

NumPy-style: shape and strides

The logical structure of NumPy-style arrays is defined by itemsize, ndim, shape and strides.

If ndim == 0, the memory location pointed to by buf is interpreted as a scalar of size itemsize. In that case, both shape and strides are NULL.

If strides is NULL, the array is interpreted as a standard n-dimensional C-array. Otherwise, the consumer must access an n-dimensional array as follows:

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

As noted above, buf can point to any location within the actual memory block. An exporter can check the validity of a buffer with this function:

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-style: shape, strides and suboffsets

In addition to the regular items, PIL-style arrays can contain pointers that must be followed in order to get to the next element in a dimension. For example, the regular three-dimensional C-array char v[2][2][3] can also be viewed as an array of 2 pointers to 2 two-dimensional arrays: char (*v[2])[2][3]. In suboffsets representation, those two pointers can be embedded at the start of buf, pointing to two char x[2][3] arrays that can be located anywhere in memory.

Here is a function that returns a pointer to the element in an N-D array pointed to by an N-dimensional index when there are both non-NULL strides and suboffsets:

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;
}