Назначение в Python 3.x буферов с размерами элементов> 1
Я пытаюсь выставить буфер информации о пикселях изображения (32-битный RGBA) через интерфейс буфера Python 3.x. После долгой игры я смог заставить это работать так:
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
, что является позором и немного сбивает с толку (я определил, что формат буфера был "я" в конце концов), но я могу с этим справиться. В случаях 2 и 3 все становится действительно странным: оба случая дают мне чтение TypeError mismatching item sizes for "my.Image" and "bytes"
(Куда my.Image
это, очевидно, мой тип изображения)
Это очень смущает меня, так как данные, которые я передаю, очевидно, имеют тот же размер, что и данные, которые я получаю из этого элемента. Кажется, что буферы просто прекращают разрешать присваивание, если размер элементов больше 1. Конечно, документация для этого интерфейса действительно скудна, и просмотр кода на Python на самом деле не дает никаких примеров использования, поэтому я довольно застрял. Я пропускаю какой-то фрагмент документации, в котором говорится, что "буферы становятся практически бесполезными, когда размер элемента> 1", я делаю что-то не так, что не вижу, или это ошибка в Python? (Тестирование по 3.1.1)
Спасибо за любую информацию, которую вы можете дать по этому (по общему признанию) вопросу!
1 ответ
Я нашел это в коде python (в memoryobject.c в Objects) в функции memory_ass_sub:
/* 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;
}
это источник последних двух ошибок. Похоже, что размер элемента даже для mv[0] все еще не равен самому себе.
Обновить
Вот что я думаю происходит. Когда вы пытаетесь назначить что-то в mv, он вызывает memory_ass_sub в Objects/memoryobject.c, но эта функция принимает в качестве входных данных только PyObject. Затем этот объект преобразуется в буфер внутри с помощью функции PyObject_GetBuffer, даже если в случае mv[0] это уже буфер (и буфер, который вы хотите!). Я предполагаю, что эта функция берет объект и превращает его в простой буфер itemsize=1 независимо от того, является ли он буфером или нет. Вот почему вы получаете несоответствующие размеры элемента даже для
mv[0] = mv[0]
Проблема с первым заданием,
mv[0] = 0xFFFFFFFF
вытекает (я думаю) из проверки, может ли int использоваться в качестве буфера, который в настоящее время он не настроен для того, что я понимаю.
Другими словами, буферная система в настоящее время не может обрабатывать элементы размером больше 1. Не похоже, что это слишком далеко, но это потребовало бы немного больше работы с вашей стороны. Если вы все заработаете, вам, вероятно, следует отправить изменения обратно в основной дистрибутив Python.
Еще одно обновление
Код ошибки с вашей первой попытки присвоения mv[0] происходит из-за того, что int завершается с ошибкой PyObject_CheckBuffer при вызове PyObject_CheckBuffer. По-видимому, система обрабатывает только копии с буферизированных объектов. Кажется, это тоже нужно изменить.
Заключение
В настоящее время буферная система Python не может обрабатывать элементы с itemsize > 1, как вы уже догадались. Кроме того, он не может обрабатывать назначения в буфер из небуферируемых объектов, таких как целые.