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.
This commit is contained in:
Antoine Pitrou 2010-09-09 12:59:39 +00:00
parent bad3c88094
commit 6e6cc830c4
4 changed files with 158 additions and 11 deletions

View File

@ -2311,7 +2311,40 @@ is generally interpreted as simple bytes.
Notice how the size of the memoryview object cannot be changed. 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() .. method:: tobytes()

View File

@ -225,6 +225,51 @@ class AbstractMemoryTests:
gc.collect() gc.collect()
self.assertTrue(wr() is None, wr()) 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 # Variations on source objects for the buffer: bytes-like objects, then arrays
# with itemsize > 1. # with itemsize > 1.

View File

@ -10,6 +10,10 @@ What's New in Python 3.2 Alpha 3?
Core and Builtins 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 - Issue #9797: pystate.c wrongly assumed that zero couldn't be a valid
thread-local storage key. thread-local storage key.

View File

@ -3,6 +3,23 @@
#include "Python.h" #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 static Py_ssize_t
get_shape0(Py_buffer *buf) get_shape0(Py_buffer *buf)
{ {
@ -34,6 +51,7 @@ static int
memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags) memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags)
{ {
int res = 0; int res = 0;
CHECK_RELEASED_INT(self);
/* XXX for whatever reason fixing the flags seems necessary */ /* XXX for whatever reason fixing the flags seems necessary */
if (self->view.readonly) if (self->view.readonly)
flags &= ~PyBUF_WRITABLE; flags &= ~PyBUF_WRITABLE;
@ -330,12 +348,14 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
static PyObject * static PyObject *
memory_format_get(PyMemoryViewObject *self) memory_format_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return PyUnicode_FromString(self->view.format); return PyUnicode_FromString(self->view.format);
} }
static PyObject * static PyObject *
memory_itemsize_get(PyMemoryViewObject *self) memory_itemsize_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return PyLong_FromSsize_t(self->view.itemsize); return PyLong_FromSsize_t(self->view.itemsize);
} }
@ -366,30 +386,35 @@ _IntTupleFromSsizet(int len, Py_ssize_t *vals)
static PyObject * static PyObject *
memory_shape_get(PyMemoryViewObject *self) memory_shape_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return _IntTupleFromSsizet(self->view.ndim, self->view.shape); return _IntTupleFromSsizet(self->view.ndim, self->view.shape);
} }
static PyObject * static PyObject *
memory_strides_get(PyMemoryViewObject *self) memory_strides_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return _IntTupleFromSsizet(self->view.ndim, self->view.strides); return _IntTupleFromSsizet(self->view.ndim, self->view.strides);
} }
static PyObject * static PyObject *
memory_suboffsets_get(PyMemoryViewObject *self) memory_suboffsets_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return _IntTupleFromSsizet(self->view.ndim, self->view.suboffsets); return _IntTupleFromSsizet(self->view.ndim, self->view.suboffsets);
} }
static PyObject * static PyObject *
memory_readonly_get(PyMemoryViewObject *self) memory_readonly_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return PyBool_FromLong(self->view.readonly); return PyBool_FromLong(self->view.readonly);
} }
static PyObject * static PyObject *
memory_ndim_get(PyMemoryViewObject *self) memory_ndim_get(PyMemoryViewObject *self)
{ {
CHECK_RELEASED(self);
return PyLong_FromLong(self->view.ndim); return PyLong_FromLong(self->view.ndim);
} }
@ -408,6 +433,7 @@ static PyGetSetDef memory_getsetlist[] ={
static PyObject * static PyObject *
memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs) memory_tobytes(PyMemoryViewObject *mem, PyObject *noargs)
{ {
CHECK_RELEASED(mem);
return PyObject_CallFunctionObjArgs( return PyObject_CallFunctionObjArgs(
(PyObject *) &PyBytes_Type, mem, NULL); (PyObject *) &PyBytes_Type, mem, NULL);
} }
@ -423,6 +449,7 @@ memory_tolist(PyMemoryViewObject *mem, PyObject *noargs)
PyObject *res, *item; PyObject *res, *item;
char *buf; char *buf;
CHECK_RELEASED(mem);
if (strcmp(view->format, "B") || view->itemsize != 1) { if (strcmp(view->format, "B") || view->itemsize != 1) {
PyErr_SetString(PyExc_NotImplementedError, PyErr_SetString(PyExc_NotImplementedError,
"tolist() only supports byte views"); "tolist() only supports byte views");
@ -449,17 +476,9 @@ memory_tolist(PyMemoryViewObject *mem, PyObject *noargs)
return res; 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 static void
memory_dealloc(PyMemoryViewObject *self) do_release(PyMemoryViewObject *self)
{ {
_PyObject_GC_UNTRACK(self);
if (self->view.obj != NULL) { if (self->view.obj != NULL) {
if (self->base && PyTuple_Check(self->base)) { if (self->base && PyTuple_Check(self->base)) {
/* Special case when first element is generic object /* Special case when first element is generic object
@ -484,19 +503,57 @@ memory_dealloc(PyMemoryViewObject *self)
} }
Py_CLEAR(self->base); 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); PyObject_GC_Del(self);
} }
static PyObject * static PyObject *
memory_repr(PyMemoryViewObject *self) 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 */ /* Sequence methods */
static Py_ssize_t static Py_ssize_t
memory_length(PyMemoryViewObject *self) memory_length(PyMemoryViewObject *self)
{ {
CHECK_RELEASED_INT(self);
return get_shape0(&self->view); return get_shape0(&self->view);
} }
@ -508,6 +565,7 @@ memory_item(PyMemoryViewObject *self, Py_ssize_t result)
{ {
Py_buffer *view = &(self->view); Py_buffer *view = &(self->view);
CHECK_RELEASED(self);
if (view->ndim == 0) { if (view->ndim == 0) {
PyErr_SetString(PyExc_IndexError, PyErr_SetString(PyExc_IndexError,
"invalid indexing of 0-dim memory"); "invalid indexing of 0-dim memory");
@ -557,6 +615,7 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key)
Py_buffer *view; Py_buffer *view;
view = &(self->view); view = &(self->view);
CHECK_RELEASED(self);
if (view->ndim == 0) { if (view->ndim == 0) {
if (key == Py_Ellipsis || if (key == Py_Ellipsis ||
(PyTuple_Check(key) && PyTuple_GET_SIZE(key)==0)) { (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); Py_buffer *view = &(self->view);
char *srcbuf, *destbuf; char *srcbuf, *destbuf;
CHECK_RELEASED_INT(self);
if (view->readonly) { if (view->readonly) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"cannot modify read-only memory"); "cannot modify read-only memory");
@ -718,6 +778,11 @@ memory_richcompare(PyObject *v, PyObject *w, int op)
ww.obj = NULL; ww.obj = NULL;
if (op != Py_EQ && op != Py_NE) if (op != Py_EQ && op != Py_NE)
goto _notimpl; 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) { if (PyObject_GetBuffer(v, &vv, PyBUF_CONTIG_RO) == -1) {
PyErr_Clear(); PyErr_Clear();
goto _notimpl; goto _notimpl;