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:
parent
bad3c88094
commit
6e6cc830c4
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue