summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-09-09 12:59:39 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2010-09-09 12:59:39 (GMT)
commit6e6cc830c4968f040ded1e474db86a9aeb37c33e (patch)
tree4d374c13135309484270452752e97135055d78e0
parentbad3c88094f43f3bc7dcce22f47b8c2a8dddabcf (diff)
downloadcpython-6e6cc830c4968f040ded1e474db86a9aeb37c33e.zip
cpython-6e6cc830c4968f040ded1e474db86a9aeb37c33e.tar.gz
cpython-6e6cc830c4968f040ded1e474db86a9aeb37c33e.tar.bz2
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.
-rw-r--r--Doc/library/stdtypes.rst35
-rw-r--r--Lib/test/test_memoryview.py45
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/memoryobject.c85
4 files changed, 158 insertions, 11 deletions
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index a732fb2..b4c3eda 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 "<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()
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index 8e56df9..d56d64f 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 0849bd9..4966aa9 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 802a922..36626b2 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("<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 @@ 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;