バッファプロトコル
Pythonで使用可能な特定のオブジェクトは、基になるメモリ配列またはバッファへのアクセスをラップします。 このようなオブジェクトには、組み込みの bytes と bytearray 、および array.array のようないくつかの拡張タイプが含まれます。 サードパーティのライブラリは、画像処理や数値解析などの特別な目的のために独自のタイプを定義する場合があります。
これらのタイプにはそれぞれ独自のセマンティクスがありますが、おそらく大きなメモリバッファに支えられているという共通の特徴を共有しています。 その場合、状況によっては、中間コピーなしでそのバッファーに直接アクセスすることが望ましい場合があります。
Pythonは、バッファプロトコルの形式でCレベルでそのような機能を提供します。 このプロトコルには2つの側面があります。
- プロデューサー側では、タイプは「バッファーインターフェース」をエクスポートできます。これにより、そのタイプのオブジェクトは、基になるバッファーに関する情報を公開できます。 このインターフェイスについては、バッファオブジェクト構造のセクションで説明しています。
- コンシューマー側では、オブジェクトの生の基になるデータへのポインターを取得するためのいくつかの手段が利用可能です(たとえば、メソッドパラメーター)。
bytes や bytearray などの単純なオブジェクトは、基になるバッファーをバイト指向の形式で公開します。 他の形式も可能です。 たとえば、 array.array によって公開される要素はマルチバイト値にすることができます。
バッファインターフェイスのコンシューマの例は、ファイルオブジェクトの write()メソッドです。バッファインターフェイスを介して一連のバイトをエクスポートできる任意のオブジェクトをファイルに書き込むことができます。 write()
は、渡されたオブジェクトの内部コンテンツへの読み取り専用アクセスのみを必要としますが、 readinto()などの他のメソッドは、引数のコンテンツへの書き込みアクセスを必要とします。 バッファインターフェイスを使用すると、オブジェクトは読み取り/書き込みバッファと読み取り専用バッファのエクスポートを選択的に許可または拒否できます。
バッファインターフェイスのコンシューマがターゲットオブジェクト上でバッファを取得するには、次の2つの方法があります。
- 適切なパラメータを使用して PyObject_GetBuffer()を呼び出します。
y*
、w*
、またはs*
フォーマットコードのいずれかを使用して、 PyArg_ParseTuple()(またはその兄弟の1つ)を呼び出します。
どちらの場合も、バッファが不要になったときに 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 フラグなしでバッファーを要求した場合、 format は
NULL
に設定されますが、 itemsize にはまだ値があります元の形式の場合。shape が存在する場合、等式
product(shape) * itemsize == len
は引き続き保持され、コンシューマーは itemsize を使用してバッファーをナビゲートできます。PyBUF_SIMPLE または PyBUF_WRITABLE リクエストの結果として shape が
NULL
である場合、コンシューマーは itemsize を無視する必要があります。itemsize == 1
を想定します。
- const char *format
NUL で終了する文字列。 struct モジュールスタイルの構文で、単一のアイテムの内容を記述します。 これが
NULL
の場合、"B"
(符号なしバイト)が想定されます。このフィールドは、 PyBUF_FORMAT フラグによって制御されます。
- int ndim
メモリがn次元配列として表す次元の数。
0
の場合、 buf はスカラーを表す単一のアイテムを指します。 この場合、 shape 、 strides 、および suboffsets はNULL
でなければなりません。マクロ
PyBUF_MAX_NDIM
は、次元の最大数を64に制限します。 輸出業者はこの制限を尊重しなければなりません。多次元バッファの消費者は最大PyBUF_MAX_NDIM
次元を処理できる必要があります。
- Py_ssize_t *shape
長さ ndim の
Py_ssize_t
の配列で、メモリの形状をn次元配列として示します。shape[0] * ... * shape[ndim-1] * itemsize
は len と等しくなければならないことに注意してください。形状の値は
shape[n] >= 0
に制限されています。 ケースshape[n] == 0
には特別な注意が必要です。 詳細については、複雑な配列を参照してください。形状配列は、コンシューマーに対して読み取り専用です。
- Py_ssize_t *strides
長さ ndim の
Py_ssize_t
の配列で、各次元の新しい要素に到達するためにスキップするバイト数を示します。ストライド値は任意の整数にすることができます。 通常の配列の場合、ストライドは通常正ですが、コンシューマーはケース
strides[n] <= 0
を処理できなければなりません。 詳細については、複雑な配列を参照してください。ストライド配列は、コンシューマーに対して読み取り専用です。
- Py_ssize_t *suboffsets
長さ ndim の
Py_ssize_t
の配列。suboffsets[n] >= 0
の場合、n番目の次元に沿って格納される値はポインターであり、サブオフセット値は、参照解除後に各ポインターに追加するバイト数を示します。 負のサブオフセット値は、逆参照が発生してはならないことを示します(連続したメモリブロックをストライドします)。すべてのサブオフセットが負の場合(つまり、 参照解除は必要ありません)、このフィールドは
NULL
(デフォルト値)である必要があります。このタイプの配列表現は、Python Imaging Library(PIL)によって使用されます。 このような配列の要素にアクセスする方法の詳細については、複雑な配列を参照してください。
サブオフセット配列は、コンシューマーに対して読み取り専用です。
- void *internal
これは、エクスポートするオブジェクトが内部で使用するためのものです。 たとえば、これはエクスポータによって整数として再キャストされ、バッファが解放されたときにシェイプ、ストライド、およびサブオフセット配列を解放する必要があるかどうかに関するフラグを格納するために使用される場合があります。 消費者はこの値を変更してはなりません(MUSTNOT)。
- void *buf
バッファリクエストタイプ
バッファは通常、 PyObject_GetBuffer()を介してエクスポートオブジェクトにバッファ要求を送信することによって取得されます。 メモリの論理構造の複雑さは大幅に異なる可能性があるため、コンシューマーは flags 引数を使用して、処理できる正確なバッファータイプを指定します。
すべての Py_buffer フィールドは、リクエストタイプによって明確に定義されています。
読み取り専用、フォーマット
PyBUF_WRITABLE は、次のセクションの任意のフラグに| 'することができます。 PyBUF_SIMPLE は0として定義されているため、 PyBUF_WRITABLE をスタンドアロンフラグとして使用して、単純な書き込み可能なバッファーを要求できます。
PyBUF_FORMAT は、 PyBUF_SIMPLE を除くすべてのフラグに| 'dできます。 後者はすでにフォーマットB
(符号なしバイト)を意味します。
形状、ストライド、サブオフセット
メモリの論理構造を制御するフラグは、複雑さの降順でリストされています。 各フラグには、その下にあるフラグのすべてのビットが含まれていることに注意してください。
リクエスト | 形 | 歩幅 | サブオフセット |
---|---|---|---|
|
はい | はい | 必要に応じて |
|
はい | はい | ヌル |
|
はい | ヌル | ヌル |
|
ヌル | ヌル | ヌル |
隣接リクエスト
CまたはFortran contiguity は、ストライド情報の有無にかかわらず、明示的に要求できます。 ストライド情報がない場合、バッファはC連続である必要があります。
リクエスト | 形 | 歩幅 | サブオフセット | コンティグ |
---|---|---|---|---|
|
はい | はい | ヌル | C |
|
はい | はい | ヌル | F |
|
はい | はい | ヌル | CまたはF |
PyBUF_ND
|
はい | ヌル | ヌル | C |
複合リクエスト
考えられるすべての要求は、前のセクションのフラグの組み合わせによって完全に定義されます。 便宜上、バッファプロトコルは、頻繁に使用される組み合わせを単一のフラグとして提供します。
次の表で、 U は未定義の隣接を表します。 コンシューマーは、 PyBuffer_IsContiguous()を呼び出して隣接性を判別する必要があります。
リクエスト | 形 | 歩幅 | サブオフセット | コンティグ | 読み取り専用 | フォーマット |
---|---|---|---|---|---|---|
|
はい | はい | 必要に応じて | U | 0 | はい |
|
はい | はい | 必要に応じて | U | 1または0 | はい |
|
はい | はい | ヌル | U | 0 | はい |
|
はい | はい | ヌル | U | 1または0 | はい |
|
はい | はい | ヌル | U | 0 | ヌル |
|
はい | はい | ヌル | U | 1または0 | ヌル |
|
はい | ヌル | ヌル | C | 0 | ヌル |
|
はい | ヌル | ヌル | C | 1または0 | ヌル |
複雑な配列
NumPyスタイル:形状と歩幅
NumPyスタイルの配列の論理構造は、 itemsize 、 ndim 、 shape 、および strides によって定義されます。
ndim == 0
の場合、 buf が指すメモリ位置は、サイズ itemsize のスカラーとして解釈されます。 その場合、 shape と strides はどちらもNULL
です。
strides がNULL
の場合、配列は標準の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;
}