diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -94,10 +94,9 @@ :c:func:`PyBuffer_Release`. The field is the equivalent of the return value of any standard C-API function. - As a special case, for *temporary* buffers that are wrapped by - :c:func:`PyMemoryView_FromBuffer` or :c:func:`PyBuffer_FillInfo` - this field is *NULL*. In general, exporting objects MUST NOT - use this scheme. + As a special case, for buffers that act as a template for + :c:func:`PyMemoryView_FromBuffer` this field may be *NULL*. + In general, exporting objects MUST NOT use this scheme. .. c:member:: void \*buf diff --git a/Doc/c-api/memoryview.rst b/Doc/c-api/memoryview.rst --- a/Doc/c-api/memoryview.rst +++ b/Doc/c-api/memoryview.rst @@ -29,9 +29,23 @@ .. c:function:: PyObject *PyMemoryView_FromBuffer(Py_buffer *view) - Create a memoryview object wrapping the given buffer structure *view*. - For simple byte buffers, :c:func:`PyMemoryView_FromMemory` is the preferred - function. + Create a memoryview object using the given buffer structure *view* + as a template. If non-NULL, :c:member:`~Py_buffer.shape`, + :c:member:`~Py_buffer.strides` and :c:member:`~Py_buffer.suboffsets` + are copied and may be allocated on the stack. :c:member:`~Py_buffer.buf` + and :c:member:`~Py_buffer.format` must contain addresses that stay + valid during the lifetime of the returned memoryview. + + :c:member:`~Py_buffer.obj` must be either NULL or a new reference to + an object that is owned by the memoryview. :c:func:`PyBuffer_Release` + is called on :c:member:`~Py_buffer.obj` when the memoryview is deallocated. + If :c:member:`~Py_buffer.obj` is non-NULL, this means that both the + reference count of :c:member:`~Py_buffer.obj` is decremented *and*, + if present, the :c:func:`PyBufferProcs.bf_releasebuffer` method of + :c:member:`~Py_buffer.obj` is called! + + Note that for simple byte buffers :c:func:`PyMemoryView_FromMemory` is + the preferred function. .. c:function:: PyObject *PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char order) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -2431,6 +2431,26 @@ self.assertRaises(ValueError, get_contiguous, nd, PyBUF_READ, 'F') self.assertRaises(ValueError, get_contiguous, nd[::-1], PyBUF_READ, 'C') + def test_memoryview_from_buffer(self): + + lst = list(range(97, 109)) + x = ndarray(lst, shape=[2, 2, 3], format='B') + + m = memoryview_from_buffer_cleanup() + self.verify(m, obj=m.obj, + itemsize=1, fmt='B', readonly=1, + ndim=3, shape=[2, 2, 3], strides=(6, 3, 1), + lst=x.tolist()) + + self.assertEqual(m, x) + self.assertEqual(m[::-1], x[::-1]) + + y = get_contiguous(m[::-1], PyBUF_READ, 'C') + self.assertEqual(y, x[::-1]) + + y = m.cast('c', shape=[12]) + self.assertEqual(y.tolist(), [bytes(chr(x), 'latin-1') for x in range(97, 109)]) + def test_memoryview_cast_zero_shape(self): # Casts are undefined if shape contains zeros. These arrays are # regarded as C-contiguous by Numpy and PyBuffer_GetContiguous(), diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -2220,11 +2220,13 @@ const NDArrayObject *nd = (NDArrayObject *)self; const Py_buffer *view = &nd->head->base; const ndbuf_t *ndbuf; + /* stack values for format are not yet supported */ static char format[ND_MAX_NDIM+1]; - static Py_ssize_t shape[ND_MAX_NDIM]; - static Py_ssize_t strides[ND_MAX_NDIM]; - static Py_ssize_t suboffsets[ND_MAX_NDIM]; - static Py_buffer info; + /* initialize a memoryview from values on the stack */ + Py_ssize_t shape[ND_MAX_NDIM]; + Py_ssize_t strides[ND_MAX_NDIM]; + Py_ssize_t suboffsets[ND_MAX_NDIM]; + Py_buffer info; char *p; if (!ND_IS_CONSUMER(nd)) @@ -2240,6 +2242,8 @@ } info = *view; + info.obj = NULL; + p = PyMem_Realloc(infobuf, ndbuf->len); if (p == NULL) { PyMem_Free(infobuf); @@ -2285,6 +2289,40 @@ return PyMemoryView_FromBuffer(&info); } +static PyObject * +memoryview_from_buffer_cleanup(PyObject *self, PyObject *noargs) +{ + PyObject *b; + Py_buffer info; + Py_ssize_t shape[3] = {2, 2, 3}; + Py_ssize_t strides[3] = {6, 3, 1}; + const char *cp = "abcdefghijkl"; + + b = PyBytes_FromString(cp); + if (b == NULL) + return NULL; + + info.buf = PyBytes_AS_STRING(b); + /* When the returned memoryview is deallocated, PyBuffer_Release() + decrements the reference to the bytes object. NOTE: This scheme + relies on the internal knowledge that the releasebufferproc of the + bytes object does not touch format, shape, strides and suboffsets + AND does not decrement an export count! + */ + info.obj = b; + info.len = strlen(cp); + info.itemsize = 1; + info.format = "B"; + info.readonly = 1; + info.ndim = 3; + info.shape = shape; + info.strides = strides; + info.suboffsets = NULL; + info.internal = NULL; + + return PyMemoryView_FromBuffer(&info); +} + /* Get a single item from bufobj at the location specified by seq. seq is a list or tuple of indices. The purpose of this function is to check other functions against PyBuffer_GetPointer(). */ @@ -2792,6 +2830,7 @@ {"py_buffer_to_contiguous", py_buffer_to_contiguous, METH_VARARGS, NULL}, {"is_contiguous", is_contiguous, METH_VARARGS, NULL}, {"cmp_contig", cmp_contig, METH_VARARGS, NULL}, + {"memoryview_from_buffer_cleanup", memoryview_from_buffer_cleanup, METH_NOARGS, NULL}, {NULL, NULL} }; diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -756,13 +756,14 @@ } mbuf = mbuf_alloc(); - if (mbuf == NULL) + if (mbuf == NULL) { + PyBuffer_Release(info); return NULL; - - /* info->obj is either NULL or a borrowed reference. This reference - should not be decremented in PyBuffer_Release(). */ + } + + /* info->obj is either NULL or an owned reference. This reference + will be decremented in PyBuffer_Release(). */ mbuf->master = *info; - mbuf->master.obj = NULL; mv = mbuf_add_view(mbuf, NULL); Py_DECREF(mbuf);