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.
|
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()
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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,12 +503,49 @@ 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)
|
||||||
{
|
{
|
||||||
|
if (IS_RELEASED(self))
|
||||||
|
return PyUnicode_FromFormat("<released memory at %p>", self);
|
||||||
|
else
|
||||||
return PyUnicode_FromFormat("<memory at %p>", self);
|
return PyUnicode_FromFormat("<memory at %p>", self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,6 +553,7 @@ memory_repr(PyMemoryViewObject *self)
|
||||||
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;
|
||||||
|
|
Loading…
Reference in New Issue