From 480ab05d5fee2b8fa161f799af33086a4e68c7dd Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sat, 14 Apr 2018 19:49:21 +0200 Subject: [PATCH] bpo-33176: Add a toreadonly() method to memoryviews. (GH-6466) --- Doc/library/stdtypes.rst | 19 ++++++++++ Lib/test/test_buffer.py | 37 +++++++++++-------- Lib/test/test_memoryview.py | 11 ++++++ .../2018-04-13-22-31-09.bpo-33176.PB9com.rst | 1 + Objects/memoryobject.c | 19 ++++++++++ 5 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-04-13-22-31-09.bpo-33176.PB9com.rst diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index a213189a340..af2b4e1cb66 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -3591,6 +3591,25 @@ copying. :mod:`struct` module syntax as well as multi-dimensional representations. + .. method:: toreadonly() + + Return a readonly version of the memoryview object. The original + memoryview object is unchanged. :: + + >>> m = memoryview(bytearray(b'abc')) + >>> mm = m.toreadonly() + >>> mm.tolist() + [89, 98, 99] + >>> mm[0] = 42 + Traceback (most recent call last): + File "", line 1, in + TypeError: cannot modify read-only memory + >>> m[0] = 43 + >>> mm.tolist() + [43, 98, 99] + + .. versionadded:: 3.8 + .. method:: release() Release the underlying buffer exposed by the memoryview object. Many diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index f302da415d3..a3f3ef098a6 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -924,23 +924,30 @@ class TestBufferProtocol(unittest.TestCase): except BufferError: # re-exporter does not provide full information return ex = result.obj if isinstance(result, memoryview) else result - self.assertIs(m.obj, ex) - self.assertEqual(m.nbytes, expected_len) - self.assertEqual(m.itemsize, itemsize) - self.assertEqual(m.format, fmt) - self.assertEqual(m.readonly, readonly) - self.assertEqual(m.ndim, ndim) - self.assertEqual(m.shape, tuple(shape)) - if not (sliced and suboffsets): - self.assertEqual(m.strides, tuple(strides)) - self.assertEqual(m.suboffsets, tuple(suboffsets)) - n = 1 if ndim == 0 else len(lst) - self.assertEqual(len(m), n) + def check_memoryview(m, expected_readonly=readonly): + self.assertIs(m.obj, ex) + self.assertEqual(m.nbytes, expected_len) + self.assertEqual(m.itemsize, itemsize) + self.assertEqual(m.format, fmt) + self.assertEqual(m.readonly, expected_readonly) + self.assertEqual(m.ndim, ndim) + self.assertEqual(m.shape, tuple(shape)) + if not (sliced and suboffsets): + self.assertEqual(m.strides, tuple(strides)) + self.assertEqual(m.suboffsets, tuple(suboffsets)) - rep = result.tolist() if fmt else result.tobytes() - self.assertEqual(rep, lst) - self.assertEqual(m, result) + n = 1 if ndim == 0 else len(lst) + self.assertEqual(len(m), n) + + rep = result.tolist() if fmt else result.tobytes() + self.assertEqual(rep, lst) + self.assertEqual(m, result) + + check_memoryview(m) + with m.toreadonly() as mm: + check_memoryview(mm, expected_readonly=True) + m.tobytes() # Releasing mm didn't release m def verify_getbuf(self, orig_ex, ex, req, sliced=False): def simple_fmt(ex): diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index ddd5b9a8e2f..ca307d8342f 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -362,6 +362,17 @@ class AbstractMemoryTests: self.assertEqual(list(reversed(m)), aslist) self.assertEqual(list(reversed(m)), list(m[::-1])) + def test_toreadonly(self): + for tp in self._types: + b = tp(self._source) + m = self._view(b) + mm = m.toreadonly() + self.assertTrue(mm.readonly) + self.assertTrue(memoryview(mm).readonly) + self.assertEqual(mm.tolist(), m.tolist()) + mm.release() + m.tolist() + def test_issue22668(self): a = array.array('H', [256, 256, 256, 256]) x = memoryview(a) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-04-13-22-31-09.bpo-33176.PB9com.rst b/Misc/NEWS.d/next/Core and Builtins/2018-04-13-22-31-09.bpo-33176.PB9com.rst new file mode 100644 index 00000000000..68785b3cb6a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-04-13-22-31-09.bpo-33176.PB9com.rst @@ -0,0 +1 @@ +Add a ``toreadonly()`` method to memoryviews. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index ccf45ffc582..adaa67c0644 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1398,6 +1398,20 @@ error: return NULL; } +static PyObject * +memory_toreadonly(PyMemoryViewObject *self, PyObject *noargs) +{ + CHECK_RELEASED(self); + /* Even if self is already readonly, we still need to create a new + * object for .release() to work correctly. + */ + self = (PyMemoryViewObject *) mbuf_add_view(self->mbuf, &self->view); + if (self != NULL) { + self->view.readonly = 1; + }; + return (PyObject *) self; +} + /**************************************************************************/ /* getbuffer */ @@ -3061,6 +3075,10 @@ PyDoc_STRVAR(memory_cast_doc, "cast($self, /, format, *, shape)\n--\n\ \n\ Cast a memoryview to a new format or shape."); +PyDoc_STRVAR(memory_toreadonly_doc, +"toreadonly($self, /)\n--\n\ +\n\ +Return a readonly version of the memoryview."); static PyMethodDef memory_methods[] = { {"release", (PyCFunction)memory_release, METH_NOARGS, memory_release_doc}, @@ -3068,6 +3086,7 @@ static PyMethodDef memory_methods[] = { {"hex", (PyCFunction)memory_hex, METH_NOARGS, memory_hex_doc}, {"tolist", (PyCFunction)memory_tolist, METH_NOARGS, memory_tolist_doc}, {"cast", (PyCFunction)memory_cast, METH_VARARGS|METH_KEYWORDS, memory_cast_doc}, + {"toreadonly", (PyCFunction)memory_toreadonly, METH_NOARGS, memory_toreadonly_doc}, {"__enter__", memory_enter, METH_NOARGS, NULL}, {"__exit__", memory_exit, METH_VARARGS, NULL}, {NULL, NULL}