分配给 Python 3.x 缓冲区,其中 itemsize > 1
我试图通过 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' 不支持缓冲区接口
,这是一种耻辱,而且有点令人困惑(毕竟我确实指定了缓冲区格式是“I”),但是我可以处理这个问题。不过,在情况 2 和情况 3 中,事情变得非常奇怪:这两种情况都会给我一个 TypeError,读取“my.Image”和“bytes”的项目大小不匹配(其中 my.Image
显然是我的图像类型)
这对我来说非常令人困惑,因为我传入的数据显然与我从该元素中获取的数据大小相同。如果 itemsize 大于 1,缓冲区似乎就停止允许分配。当然,这个接口的文档非常稀疏,并且仔细阅读 python 代码并没有真正给出任何使用示例,所以我陷入了困境。我是否遗漏了一些文档片段,其中指出“当 itemsize > 1 时,缓冲区基本上变得无用”,我是否做了一些我看不到的错误,或者这是 Python 中的一个错误? (针对 3.1.1 进行测试)
感谢您对这个(公认的高级)问题提供的任何见解!
I am trying to expose a buffer of image pixel information (32 bit RGBA) through the Python 3.x buffer interface. After quite a bit of playing around, I was able to get this working like so:
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);
}
And in python I can play with it like so:
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
And that works splendidly. However, it would be great if I could work with the full pixel value (int, 4 byte) instead of individual bytes, so I modified the buffer fetch like so:
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;
}
This actually returns the data and I can read it correctly, but any attempt to assign a value into it now fails!
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)
In case 1 the error informs me that 'int' does not support the buffer interface
, which is a shame and a bit confusing (I did specify that the buffer format was "I" after all), but I can deal with that. In case 2 and 3 things get really weird, though: Both cases gime me an TypeError reading mismatching item sizes for "my.Image" and "bytes"
(Where my.Image
is, obviously, my image type)
This is very confusing to me, since the data I'm passing in is obviously the same size as what I get out of that element. It seems as though buffers simply stop allowing assignment if the itemsize is greater than 1. Of course, the documentation for this interface is really sparse and perusing through the python code doesn't really give any usage examples so I'm fairly stuck. Am I missing some snippit of documentation that states "buffers become essentially useless when itemsize > 1", am I doing something wrong that I can't see, or is this a bug in Python? (Testing against 3.1.1)
Thanks for any insight you can give on this (admittedly advanced) issue!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我在函数 memory_ass_sub 的 python 代码(在对象中的 memoryobject.c 中)中找到了这一点:
这是后两个错误的根源。看起来即使 mv[0] 的 itemsize 仍然不等于它本身。
更新
这就是我认为正在发生的事情。当您尝试在 mv 中分配某些内容时,它会调用 Objects/memoryobject.c 中的 memory_ass_sub ,但该函数仅接受 PyObject 作为输入。然后使用 PyObject_GetBuffer 函数将该对象更改为内部缓冲区,即使在 mv[0] 的情况下它已经是一个缓冲区(并且是您想要的缓冲区!)。我的猜测是,这个函数获取对象并将其放入 itemsize=1 的简单缓冲区中,无论它是否已经是缓冲区。这就是为什么即使
第一次分配的问题,
mv[0] = 0xFFFFFFFF
也会得到不匹配的项目大小(我认为)源于检查 int 是否能够用作缓冲区,目前尚未设置缓冲区据我了解。
换句话说,缓冲系统当前无法处理大于 1 的项目大小。看起来并不遥远,但您需要做更多的工作。如果你确实让它工作了,你可能应该将更改提交回主要的 Python 发行版。
另一个更新
第一次尝试分配 mv[0] 时的错误代码源于调用 PyObject_CheckBuffer 时 int 失败。显然,系统只处理可缓冲对象的副本。这似乎也应该改变一下。
结论
目前,Python 缓冲系统无法处理 itemsize > 的项目。 1 正如你所猜测的。此外,它无法处理不可缓冲对象(例如整数)对缓冲区的分配。
I found this in the python code (in memoryobject.c in Objects) in the function memory_ass_sub:
that's the source of the latter two errors. It looks like the itemsize for even mv[0] is still not equal to itself.
Update
Here's what I think is going on. When you try to assign something in mv, it calls memory_ass_sub in Objects/memoryobject.c, but that function takes only a PyObject as input. This object is then changed into a buffer inside using the PyObject_GetBuffer function even though in the case of mv[0] it is already a buffer (and the buffer you want!). My guess is that this function takes the object and makes it into a simple buffer of itemsize=1 regardless of whether it is already a buffer or not. That is why you get the mismatching item sizes even for
The problem with the first assignment,
mv[0] = 0xFFFFFFFF
stems (I think) from checking if the int is able to be used as a buffer, which currently it isn't set-up for from what I understand.
In other words, the buffer system isn't currently able to handle item sizes bigger from 1. It doesn't look like it is so far off, but it would take a bit more work on your end. If you do get it working, you should probably submit the changes back to the main Python distribution.
Another Update
The error code from your first try at assigning mv[0] stems from the int failing the PyObject_CheckBuffer when PyObject_CheckBuffer is called on it. Apparently the system only handles copies from bufferable objects. This seems like it should be changed too.
Conclusion
Currently the Python buffer system can't handle items with itemsize > 1 as you guessed. Also, it can't handle assignments to a buffer from non-bufferable objects such as ints.