Index: Objects/memoryobject.c =================================================================== --- Objects/memoryobject.c (révision 84451) +++ Objects/memoryobject.c (copie de travail) @@ -3,6 +3,24 @@ #include "Python.h" +#define IS_RELEASED(memobj) \ + (((PyMemoryViewObject *) memobj)->view.obj == NULL && \ + ((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 +52,7 @@ 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 +349,14 @@ 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 +387,35 @@ 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 +434,7 @@ static PyObject * memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs) { + CHECK_RELEASED(mem); return PyObject_CallFunctionObjArgs( (PyObject *) &PyBytes_Type, mem, NULL); } @@ -423,6 +450,7 @@ 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 +477,9 @@ 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) +memory_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,12 +504,46 @@ } 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) +{ + memory_release((PyMemoryViewObject *) self); + Py_RETURN_NONE; +} + +static PyMethodDef memory_methods[] = { + {"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); + memory_release(self); PyObject_GC_Del(self); } static PyObject * memory_repr(PyMemoryViewObject *self) { + CHECK_RELEASED(self); return PyUnicode_FromFormat("", self); } @@ -497,6 +551,7 @@ static Py_ssize_t memory_length(PyMemoryViewObject *self) { + CHECK_RELEASED_INT(self); return get_shape0(&self->view); } @@ -508,6 +563,7 @@ { Py_buffer *view = &(self->view); + CHECK_RELEASED(self); if (view->ndim == 0) { PyErr_SetString(PyExc_IndexError, "invalid indexing of 0-dim memory"); @@ -557,6 +613,7 @@ 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 +683,7 @@ 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 +776,11 @@ 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; Index: Doc/library/stdtypes.rst =================================================================== --- Doc/library/stdtypes.rst (révision 84451) +++ Doc/library/stdtypes.rst (copie de travail) @@ -2367,7 +2367,22 @@ .. memoryview.suboffsets isn't documented because it only seems useful for C + .. versionadded:: 3.2 + memoryview objects now support the context manager protocol. This + allows timely release of the underlying buffer, which can be useful + in order to control resource consumption. Any operation after the + end of the ``with`` block will raise a ValueError: + >>> 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 + + .. _typecontextmanager: Context Manager Types Index: Lib/test/test_memoryview.py =================================================================== --- Lib/test/test_memoryview.py (révision 84451) +++ Lib/test/test_memoryview.py (copie de travail) @@ -225,6 +225,30 @@ gc.collect() self.assertTrue(wr() is None, wr()) + 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) + check = self.assertRaisesRegexp(ValueError, "released") + with check: + m.readonly + 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) + self.assertEqual(m, m) + self.assertNotEqual(m, memoryview(b'')) + self.assertNotEqual(m, b'') # Variations on source objects for the buffer: bytes-like objects, then arrays # with itemsize > 1.