バッファプロトコル (buffer Protocol)¶
Pythonで利用可能ないくつかのオブジェクトは、下層にあるメモリ配列または buffer へのアクセスを提供します。このようなオブジェクトとして、組み込みの bytes
や bytearray
、 array.array
のようないくつかの拡張型が挙げられます。サードバーティのライブラリは画像処理や数値解析のような特別な目的のために、それら自身の型を定義することができます。
それぞれの型はそれ自身のセマンティクスを持ちますが、おそらく大きなメモリバッファからなるという共通の特徴を共有します。いくつかの状況では仲介するコピーを行うことなく直接バッファにアクセスすることが望まれます。
Pythonは buffer protocol の形式で C レベルの仕組みを提供します。このプロトコルには二つの側面があります:
提供する側では、ある型は、そのオブジェクトの下層にあるバッファに関する情報を提供できる "buffer インタフェース" をエクスポートすることができます。このインタフェースは バッファオブジェクト構造体 (buffer object structure) の節で説明します。
利用する側では、オブジェクトの下層にある生データへのポインタを得るいくつかの手段が利用できます(たとえばメソッド引数)。
bytes
や bytearray
などのシンプルなオブジェクトは、内部のバッファーをバイト列の形式で公開します。
バイト列以外の形式も利用可能です。例えば、 array.array
が公開する要素はマルチバイト値になることがあります。
bufferインタフェースの利用者の一例は、ファイルオブジェクトの write()
メソッドです: bufferインタフェースを通して一連のバイト列を提供できるどんなオブジェクトでもファイルに書き込むことができます。 write()
は、その引数として渡されたオブジェクトの内部要素に対する読み出し専用アクセスのみを必要としますが、 readinto()
のような他のメソッドでは、その引数の内容に対する書き込みアクセスが必要です。bufferインタフェースにより、オブジェクトは読み書き両方、読み出し専用バッファへのアクセスを許可するかそれとも拒否するか選択することができます。
bufferインタフェースの利用者には、対象となるオブジェクトのバッファを得る二つの方法があります:
正しい引数で
PyObject_GetBuffer()
を呼び出す;PyArg_ParseTuple()
(またはその同族のひとつ) をy*
、w*
またはs*
format codes のいずれかとともに呼び出す。
どちらのケースでも、bufferが必要なくなった時に PyBuffer_Release()
を呼び出さなければなりません。これを怠ると、リソースリークのような様々な問題につながる恐れがあります。
buffer 構造体¶
バッファ構造体(または単純に "buffers")は別のオブジェクトのバイナリデータをPythonプログラマに提供するのに便利です。これはまた、ゼロコピースライシング機構としても使用できます。このメモリブロックを参照する機能を使うことで、どんなデータでもとても簡単にPythonプログラマに提供することができます。メモリは、C 拡張の大きな配列定数かもしれませんし、オペレーティングシステムライブラリに渡す前のメモリブロックかもしれませんし、構造化データをネイティブのインメモリ形式受け渡すのに使用されるかもしれません。
Pythonインタプリタによって提供される多くのデータ型とは異なり、バッファは PyObject
ポインタではなく、シンプルなC 構造体です。そのため、作成とコピーが非常に簡単に行えます。バッファの一般的なラッパーが必要なときは、 memoryview オブジェクトが作成されます。
エクスポートされるオブジェクトを書く方法の短い説明には、 Buffer Object Structures を参照してください。バッファを取得するには、 PyObject_GetBuffer()
を参照してください。
-
Py_buffer
¶ -
void *
buf
¶ バッファフィールドが表している論理構造の先頭を指すポインタ。 バッファを提供するオブジェクトの下層物理メモリブロック中のどの位置にもなりえます。 例えば
strides
が負だと、この値はメモリブロックの末尾かもしれません。連続 配列の場合この値はメモリブロックの先頭を指します。
-
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
フラグを設定することなくバッファを要求した場合、format
はNULL
に設定されます。 しかしitemsize
は元のフォーマットに従った値を保持します。shape
が存在する場合、product(shape) * itemsize == len
の等式が守られ、利用者はitemsize
を buffer を読むために利用できます。PyBUF_SIMPLE
またはPyBUF_WRITABLE
で要求した結果、shape
がNULL
であれば、消費者はitemsize
を無視してitemsize == 1
と見なさなければなりません。
-
const char *
format
¶ 要素一つ分の内容を指定する、
struct
モジュールスタイル文法の、 NUL 終端文字列。 このポインタの値がNULL
なら、"B"
(符号無しバイト) として扱われます。このフィールドは
PyBUF_FORMAT
フラグによって制御されます。
-
int
ndim
¶ メモリがN次元配列を表している時の次元数。
0
の場合、buf
はスカラ値を表す1つの要素を指しています。 この場合、shape
,strides
,suboffsets
はNULL
でなければなりません。PyBUF_MAX_NDIM
は次元数の最大値を64に制限しています。 提供側はこの制限を尊重しなければなりません。 多次元配列の消費側はPyBUF_MAX_NDIM
次元までを扱えるようにするべきです。
-
Py_ssize_t *
shape
¶ メモリ上のN次元配列の形を示す、長さが
ndim
であるPy_ssize_t
の配列です。shape[0] * ... * shape[ndim-1] * itemsize
はlen
と等しくなければなりません。shape の値は
shape[n] >= 0
に制限されます。shape[n] == 0
の場合に特に注意が必要です。 詳細は complex arrays を参照してください。shepe (形状) 配列は利用者からは読み出し専用です。
-
Py_ssize_t *
strides
¶ 各次元において新しい値を得るためにスキップするバイト数を示す、長さ
ndim
のPy_ssize_t
の配列。ストライド値は、任意の整数を指定できます。規定の配列では、ストライドは通常でいけば有効です。しかし利用者は、
strides[n] <= 0
のケースを処理することができる必要があります。詳細については complex arrays を参照してください。消費者にとって、この strides 配列は読み出し専用です。
-
Py_ssize_t *
suboffsets
¶ Py_ssize_t
型の要素を持つ長さndim
の配列。suboffsets[n] >= 0
の場合は、 n 番目の次元に沿って保存されている値はポインタで、 suboffset 値は各ポインタの参照を解決した後に何バイト加えればいいかを示しています。 suboffset の値が負の数の場合は、ポインタの参照解決は不要 (連続したメモリブロック内に直接配置されいる) ということになります。全ての suboffset が負数の場合 (つまり参照解決が不要) な場合、このフィールドは
NULL
(デフォルト値) でなければなりません。この種の配列表現は Python Imaging Library (PIL) で使われています。 このような配列で要素にアクセスする方法についてさらに詳しことは complex arrays を参照してください。
消費者にとって、suboffsets 配列は読み出し専用です。
-
void *
internal
¶ バッファを提供する側のオブジェクトが内部的に利用するための変数です。例えば、提供側はこの変数に整数型をキャストして、shape, strides, suboffsets といった配列をバッファを開放するときに同時に解放するべきかどうかを管理するフラグに使うことができるでしょう。バッファを受け取る側は、この値を決して変更してはなりません。
-
void *
バッファリクエストのタイプ¶
バッファは通常、 PyObject_GetBuffer()
を使うことで、エクスポートするオブジェクトにバッファリクエストを送ることで得られます。メモリの論理的な構造の複雑性は多岐にわたるため、消費者は flags 引数を使って、自身が扱えるバッファの種類を指定します。
Py_buffer
の全フィールドは、リクエストの種類によって曖昧さを残さずに定義されます。
リクエストに依存しないフィールド¶
下記のフィールドは flags の影響を受けずに、常に正しい値で設定されます。: obj
, buf
, len
, itemsize
, ndim
.
readonly, format¶
PyBUF_WRITABLE
は、次の節に出てくるどのフラグとも | を取ってかまいません。
PyBUF_SIMPLE
は 0 と定義されているので、 PyBUF_WRITABLE
は単純な書き込み可能なバッファを要求する単独のフラグとして使えます。
PyBUF_FORMAT
は、PyBUF_SIMPLE
以外のどのフラグとも | を取ってかまいません。
後者のフラグは B
(符号なしバイト) フォーマットを既に指示しています。
shape, strides, suboffsets¶
このフラグは、以下で複雑性が大きい順に並べたメモリの論理的な構造を制御します。個々のフラグは、それより下に記載されたフラグのすべてのビットを含むことに注意してください。
リクエスト |
shape |
strides |
suboffsets |
---|---|---|---|
|
yes |
yes |
必要な場合 |
|
yes |
yes |
NULL |
|
yes |
NULL |
NULL |
|
NULL |
NULL |
NULL |
隣接性のリクエスト¶
ストライドの情報があってもなくても、C または Fortran の 連続性 が明確に要求される可能性があります。 ストライド情報なしに、バッファーは C と隣接している必要があります。
リクエスト |
shape |
strides |
suboffsets |
contig |
---|---|---|---|---|
|
yes |
yes |
NULL |
C |
|
yes |
yes |
NULL |
F |
|
yes |
yes |
NULL |
C か F |
yes |
NULL |
NULL |
C |
複合リクエスト¶
有り得る全てのリクエストの値は、前の節でのフラグの組み合わせで網羅的に定義されています。 便利なように、バッファープロトコルでは頻繁に使用される組み合わせを単一のフラグとして提供してます。
次のテーブルの U は連続性が未定義であることを表します。
利用者は PyBuffer_IsContiguous()
を呼び出して連続性を判定する必要があるでしょう。
リクエスト |
shape |
strides |
suboffsets |
contig |
readonly |
format |
---|---|---|---|---|---|---|
|
yes |
yes |
必要な場合 |
U |
0 |
yes |
|
yes |
yes |
必要な場合 |
U |
1 か 0 |
yes |
|
yes |
yes |
NULL |
U |
0 |
yes |
|
yes |
yes |
NULL |
U |
1 か 0 |
yes |
|
yes |
yes |
NULL |
U |
0 |
NULL |
|
yes |
yes |
NULL |
U |
1 か 0 |
NULL |
|
yes |
NULL |
NULL |
C |
0 |
NULL |
|
yes |
NULL |
NULL |
C |
1 か 0 |
NULL |
複雑な配列¶
NumPy スタイル: shape, strides¶
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 スタイル: shape, strides, suboffsets¶
PIL スタイルの配列では通常の要素の他に、ある次元の上で次の要素を取得するために辿るポインタを持てます。
例えば、通常の3次元 C 配列 char v[2][2][3]
は、2次元配列への 2 つのポインタからなる配列 char (*v[2])[2][3]
と見ることもできます。
suboffset 表現では、これらの 2 つのポインタは buf
の先頭に埋め込め、メモリのどこにでも配置できる 2 つの char x[2][3]
配列を指します。
次の例は、 strides も suboffsets も NULL
でない場合の、N 次元インデックスによって指されている N 次元配列内の要素へのポインタを返す関数です:
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;
}