[Python-checkins] r84649 - in python/branches/py3k: Doc/library/stdtypes.rst Lib/test/test_memoryview.py Misc/NEWS Objects/memoryobject.c

antoine.pitrou python-checkins at python.org
Thu Sep 9 14:59:39 CEST 2010


Author: antoine.pitrou
Date: Thu Sep  9 14:59:39 2010
New Revision: 84649

Log:
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.



Modified:
   python/branches/py3k/Doc/library/stdtypes.rst
   python/branches/py3k/Lib/test/test_memoryview.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Objects/memoryobject.c

Modified: python/branches/py3k/Doc/library/stdtypes.rst
==============================================================================
--- python/branches/py3k/Doc/library/stdtypes.rst	(original)
+++ python/branches/py3k/Doc/library/stdtypes.rst	Thu Sep  9 14:59:39 2010
@@ -2311,7 +2311,40 @@
 
    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 "<stdin>", line 1, in <module>
+         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 "<stdin>", line 1, in <module>
+         ValueError: operation forbidden on released memoryview object
+
+      .. versionadded:: 3.2
 
    .. method:: tobytes()
 

Modified: python/branches/py3k/Lib/test/test_memoryview.py
==============================================================================
--- python/branches/py3k/Lib/test/test_memoryview.py	(original)
+++ python/branches/py3k/Lib/test/test_memoryview.py	Thu Sep  9 14:59:39 2010
@@ -225,6 +225,51 @@
             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.

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Thu Sep  9 14:59:39 2010
@@ -10,6 +10,10 @@
 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.
 

Modified: python/branches/py3k/Objects/memoryobject.c
==============================================================================
--- python/branches/py3k/Objects/memoryobject.c	(original)
+++ python/branches/py3k/Objects/memoryobject.c	Thu Sep  9 14:59:39 2010
@@ -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 @@
 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 @@
 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 @@
 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 PyObject *
 memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs)
 {
+    CHECK_RELEASED(mem);
     return PyObject_CallFunctionObjArgs(
             (PyObject *) &PyBytes_Type, mem, NULL);
 }
@@ -423,6 +449,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 +476,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)
+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 @@
         }
         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("<memory at %p>", self);
+    if (IS_RELEASED(self))
+        return PyUnicode_FromFormat("<released memory at %p>", self);
+    else
+        return PyUnicode_FromFormat("<memory at %p>", self);
 }
 
 /* Sequence methods */
 static Py_ssize_t
 memory_length(PyMemoryViewObject *self)
 {
+    CHECK_RELEASED_INT(self);
     return get_shape0(&self->view);
 }
 
@@ -508,6 +565,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 +615,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 +685,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 +778,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;


More information about the Python-checkins mailing list