diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index a732fb29446..b4c3eda9378 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2311,7 +2311,40 @@ is generally interpreted as simple bytes. Notice how the size of the memoryview object cannot be changed. - :class:`memoryview` has two methods: + :class:`memoryview` has several methods: + + .. method:: release() + + Release the underlying buffer exposed by the memoryview object. Many + objects take special actions when a view is held on them (for example, + a :class:`bytearray` would temporarily forbid resizing); therefore, + calling release() is handy to remove these restrictions (and free any + dangling resources) as soon as possible. + + After this method has been called, any further operation on the view + raises a :class:`ValueError` (except :meth:`release()` itself which can + be called multiple times):: + + >>> m = memoryview(b'abc') + >>> m.release() + >>> m[0] + Traceback (most recent call last): + File "", line 1, in + ValueError: operation forbidden on released memoryview object + + The context management protocol can be used for a similar effect, + using the ``with`` statement:: + + >>> with memoryview(b'abc') as m: + ... m[0] + ... + b'a' + >>> m[0] + Traceback (most recent call last): + File "", line 1, in + ValueError: operation forbidden on released memoryview object + + .. versionadded:: 3.2 .. method:: tobytes() diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 8e56df993fa..d56d64f52f4 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -225,6 +225,51 @@ class AbstractMemoryTests: gc.collect() self.assertTrue(wr() is None, wr()) + def _check_released(self, m, tp): + check = self.assertRaisesRegexp(ValueError, "released") + with check: bytes(m) + with check: m.tobytes() + with check: m.tolist() + with check: m[0] + with check: m[0] = b'x' + with check: len(m) + with check: m.format + with check: m.itemsize + with check: m.ndim + with check: m.readonly + with check: m.shape + with check: m.strides + with check: + with m: + pass + # str() and repr() still function + self.assertIn("released memory", str(m)) + self.assertIn("released memory", repr(m)) + self.assertEqual(m, m) + self.assertNotEqual(m, memoryview(tp(self._source))) + self.assertNotEqual(m, tp(self._source)) + + def test_contextmanager(self): + for tp in self._types: + b = tp(self._source) + m = self._view(b) + with m as cm: + self.assertIs(cm, m) + self._check_released(m, tp) + m = self._view(b) + # Can release explicitly inside the context manager + with m: + m.release() + + def test_release(self): + for tp in self._types: + b = tp(self._source) + m = self._view(b) + m.release() + self._check_released(m, tp) + # Can be called a second time (it's a no-op) + m.release() + self._check_released(m, tp) # Variations on source objects for the buffer: bytes-like objects, then arrays # with itemsize > 1. diff --git a/Misc/NEWS b/Misc/NEWS index 0849bd900c0..4966aa9a019 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,10 @@ What's New in Python 3.2 Alpha 3? Core and Builtins ----------------- +- Issue #9757: memoryview objects get a release() method to release the + underlying buffer (previously this was only done when deallocating the + memoryview), and gain support for the context management protocol. + - Issue #9797: pystate.c wrongly assumed that zero couldn't be a valid thread-local storage key. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 802a92293d1..36626b2cdd4 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3,6 +3,23 @@ #include "Python.h" +#define IS_RELEASED(memobj) \ + (((PyMemoryViewObject *) memobj)->view.buf == NULL) + +#define CHECK_RELEASED(memobj) \ + if (IS_RELEASED(memobj)) { \ + PyErr_SetString(PyExc_ValueError, \ + "operation forbidden on released memoryview object"); \ + return NULL; \ + } + +#define CHECK_RELEASED_INT(memobj) \ + if (IS_RELEASED(memobj)) { \ + PyErr_SetString(PyExc_ValueError, \ + "operation forbidden on released memoryview object"); \ + return -1; \ + } + static Py_ssize_t get_shape0(Py_buffer *buf) { @@ -34,6 +51,7 @@ static int memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags) { int res = 0; + CHECK_RELEASED_INT(self); /* XXX for whatever reason fixing the flags seems necessary */ if (self->view.readonly) flags &= ~PyBUF_WRITABLE; @@ -330,12 +348,14 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort) static PyObject * memory_format_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return PyUnicode_FromString(self->view.format); } static PyObject * memory_itemsize_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return PyLong_FromSsize_t(self->view.itemsize); } @@ -366,30 +386,35 @@ _IntTupleFromSsizet(int len, Py_ssize_t *vals) static PyObject * memory_shape_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.shape); } static PyObject * memory_strides_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.strides); } static PyObject * memory_suboffsets_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.suboffsets); } static PyObject * memory_readonly_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return PyBool_FromLong(self->view.readonly); } static PyObject * memory_ndim_get(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return PyLong_FromLong(self->view.ndim); } @@ -408,6 +433,7 @@ static PyGetSetDef memory_getsetlist[] ={ static PyObject * memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs) { + CHECK_RELEASED(mem); return PyObject_CallFunctionObjArgs( (PyObject *) &PyBytes_Type, mem, NULL); } @@ -423,6 +449,7 @@ memory_tolist(PyMemoryViewObject *mem, PyObject *noargs) PyObject *res, *item; char *buf; + CHECK_RELEASED(mem); if (strcmp(view->format, "B") || view->itemsize != 1) { PyErr_SetString(PyExc_NotImplementedError, "tolist() only supports byte views"); @@ -449,17 +476,9 @@ memory_tolist(PyMemoryViewObject *mem, PyObject *noargs) return res; } -static PyMethodDef memory_methods[] = { - {"tobytes", (PyCFunction)memory_tobytes, METH_NOARGS, NULL}, - {"tolist", (PyCFunction)memory_tolist, METH_NOARGS, NULL}, - {NULL, NULL} /* sentinel */ -}; - - static void -memory_dealloc(PyMemoryViewObject *self) +do_release(PyMemoryViewObject *self) { - _PyObject_GC_UNTRACK(self); if (self->view.obj != NULL) { if (self->base && PyTuple_Check(self->base)) { /* Special case when first element is generic object @@ -484,19 +503,57 @@ memory_dealloc(PyMemoryViewObject *self) } Py_CLEAR(self->base); } + self->view.obj = NULL; + self->view.buf = NULL; +} + +static PyObject * +memory_enter(PyObject *self, PyObject *args) +{ + CHECK_RELEASED(self); + Py_INCREF(self); + return self; +} + +static PyObject * +memory_exit(PyObject *self, PyObject *args) +{ + do_release((PyMemoryViewObject *) self); + Py_RETURN_NONE; +} + +static PyMethodDef memory_methods[] = { + {"release", memory_exit, METH_NOARGS}, + {"tobytes", (PyCFunction)memory_tobytes, METH_NOARGS, NULL}, + {"tolist", (PyCFunction)memory_tolist, METH_NOARGS, NULL}, + {"__enter__", memory_enter, METH_NOARGS}, + {"__exit__", memory_exit, METH_VARARGS}, + {NULL, NULL} /* sentinel */ +}; + + +static void +memory_dealloc(PyMemoryViewObject *self) +{ + _PyObject_GC_UNTRACK(self); + do_release(self); PyObject_GC_Del(self); } static PyObject * memory_repr(PyMemoryViewObject *self) { - return PyUnicode_FromFormat("", self); + if (IS_RELEASED(self)) + return PyUnicode_FromFormat("", self); + else + return PyUnicode_FromFormat("", self); } /* Sequence methods */ static Py_ssize_t memory_length(PyMemoryViewObject *self) { + CHECK_RELEASED_INT(self); return get_shape0(&self->view); } @@ -508,6 +565,7 @@ memory_item(PyMemoryViewObject *self, Py_ssize_t result) { Py_buffer *view = &(self->view); + CHECK_RELEASED(self); if (view->ndim == 0) { PyErr_SetString(PyExc_IndexError, "invalid indexing of 0-dim memory"); @@ -557,6 +615,7 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key) Py_buffer *view; view = &(self->view); + CHECK_RELEASED(self); if (view->ndim == 0) { if (key == Py_Ellipsis || (PyTuple_Check(key) && PyTuple_GET_SIZE(key)==0)) { @@ -626,6 +685,7 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value) Py_buffer *view = &(self->view); char *srcbuf, *destbuf; + CHECK_RELEASED_INT(self); if (view->readonly) { PyErr_SetString(PyExc_TypeError, "cannot modify read-only memory"); @@ -718,6 +778,11 @@ memory_richcompare(PyObject *v, PyObject *w, int op) ww.obj = NULL; if (op != Py_EQ && op != Py_NE) goto _notimpl; + if ((PyMemoryView_Check(v) && IS_RELEASED(v)) || + (PyMemoryView_Check(w) && IS_RELEASED(w))) { + equal = (v == w); + goto _end; + } if (PyObject_GetBuffer(v, &vv, PyBUF_CONTIG_RO) == -1) { PyErr_Clear(); goto _notimpl;