itemsize > 1 の Python 3.x バッファへの代入
-
21-09-2019 - |
質問
Python 3.x バッファ インターフェイスを介して画像ピクセル情報 (32 ビット RGBA) のバッファを公開しようとしています。かなり遊んだ結果、次のように動作させることができました。
int Image_get_buffer(PyObject* self, Py_buffer* view, int flags)
{
int img_len;
void* img_bytes;
// Do my image fetch magic
get_image_pixel_data(self, &img_bytes, &img_len);
// Let python fill my buffer
PyBuffer_FillInfo(view, self, img_bytes, img_len, 0, flags);
}
そしてPythonでは次のように遊ぶことができます:
mv = memoryview(image)
print(mv[0]) # prints b'\x00'
mv[0] = b'\xFF' # set the first pixels red component to full
mx[0:4] = b'\xFF\xFF\xFF\xFF' # set the first pixel to white
そしてそれは見事に機能します。ただし、個々のバイトではなく完全なピクセル値 (int、4 バイト) を操作できれば素晴らしいことなので、バッファーフェッチを次のように変更しました。
int Image_get_buffer(PyObject* self, Py_buffer* view, int flags)
{
int img_len;
void* img_bytes;
// Do my image fetch magic
get_image_pixel_data(self, &img_bytes, &img_len);
// Fill my buffer manually (derived from the PyBuffer_FillInfo source)
Py_INCREF(self);
view->readonly = 0;
view->obj = self;
view->buf = img_bytes;
view->itemsize = 4;
view->ndim = 1;
view->len = img_len;
view->suboffsets = NULL;
view->format = NULL;
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
view->format = "I";
view->shape = NULL;
if ((flags & PyBUF_ND) == PyBUF_ND)
{
Py_ssize_t shape[] = { (int)(img_len/4) };
view->shape = shape;
}
view->strides = NULL;
if((flags & PyBUF_STRIDED) == PyBUF_STRIDED)
{
Py_ssize_t strides[] = { 4 };
view->strides = strides;
}
return 0;
}
これは実際にデータを返し、それを正しく読み取ることができますが、値を代入しようとすると失敗します。
mv = memoryview(image)
print(mv[0]) # prints b'\x00\x00\x00\x00'
mv[0] = 0xFFFFFFFF # ERROR (1)
mv[0] = b'\xFF\xFF\xFF\xFF' # ERROR! (2)
mv[0] = mv[0] # ERROR?!? (3)
ケース 1 の場合、エラーは次のことを示します。 'int' does not support the buffer interface
, これは残念で少し混乱します (結局、バッファ形式が「I」であると指定しました)が、それは対処できます。ただし、ケース 2 と 3 では、状況が非常に奇妙になります。どちらの場合も TypeError の読み取り値が表示されます mismatching item sizes for "my.Image" and "bytes"
(どこ my.Image
明らかに私のイメージタイプです)
渡しているデータのサイズがその要素から取得したものと明らかに同じであるため、これは私にとって非常に混乱します。itemsize が 1 より大きい場合、バッファーは単純に割り当てを許可しなくなるように見えます。もちろん、このインターフェイスのドキュメントは非常に乏しく、Python コードを熟読しても使用例がまったく示されていないため、かなり行き詰まってしまいます。「itemsize > 1 の場合、バッファーは本質的に役に立たなくなる」というドキュメントの一部を見逃しているのでしょうか、私が目に見えない何か間違ったことをしているのでしょうか、それともこれは Python のバグなのでしょうか?(3.1.1 に対するテスト)
この (確かに高度な) 問題について洞察を提供していただきありがとうございます。
解決
これは、関数memory_ass_subのPythonコード(Objectsのmemoryobject.c内)で見つかりました。
/* XXX should we allow assignment of different item sizes
as long as the byte length is the same?
(e.g. assign 2 shorts to a 4-byte slice) */
if (srcview.itemsize != view->itemsize) {
PyErr_Format(PyExc_TypeError,
"mismatching item sizes for \"%.200s\" and \"%.200s\"",
view->obj->ob_type->tp_name, srcview.obj->ob_type->tp_name);
goto _error;
}
それが後の 2 つのエラーの原因です。mv[0] の itemsize はまだそれ自体と等しくないようです。
アップデート
これが私が考えていることです。mv で何かを割り当てようとすると、Objects/memoryobject.c のmemory_ass_sub が呼び出されますが、その関数は入力として PyObject のみを受け取ります。このオブジェクトは、mv[0] の場合にはすでにバッファー (そして必要なバッファー) であるにもかかわらず、PyObject_GetBuffer 関数を使用して内部のバッファーに変更されます。私の推測では、この関数はオブジェクトを取得し、それがすでにバッファーであるかどうかに関係なく、それを itemsize=1 の単純なバッファーにします。そのため、たとえ
mv[0] = mv[0]
最初の課題の問題は、
mv[0] = 0xFFFFFFFF
これは、int がバッファとして使用できるかどうかをチェックすることに由来していると思いますが、私が理解している限り、現時点ではそのように設定されていません。
つまり、バッファ システムは現在、1 より大きいアイテム サイズを処理できません。そう遠くないように見えますが、もう少し手間がかかるでしょう。正常に動作するようになったら、おそらく変更をメインの Python ディストリビューションに送信する必要があります。
別のアップデート
最初に mv[0] を割り当てようとしたときのエラー コードは、int が PyObject_CheckBuffer が呼び出されたときに PyObject_CheckBuffer に失敗したことに起因しています。どうやら、システムはバッファ可能なオブジェクトからのコピーのみを処理します。これも変えたほうが良さそうです。
結論
現在、Python バッファ システムは、ご想像のとおり itemsize > 1 のアイテムを処理できません。また、int などのバッファリング不可能なオブジェクトからのバッファへの代入も処理できません。