From 4ebfea6d2c2715967e0f1a144ac4c121e6c107ca Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 13 Apr 2018 22:31:15 +0200 Subject: [PATCH 1/2] bpo-33176: Add a toreadonly() method to memoryviews. --- Doc/library/stdtypes.rst | 19 ++++++++++++++++++ Lib/test/test_buffer.py | 9 +++++++++ Lib/test/test_memoryview.py | 20 +++++++++++++++++++ .../2018-04-13-22-31-09.bpo-33176.PB9com.rst | 1 + Objects/memoryobject.c | 20 +++++++++++++++++++ 5 files changed, 69 insertions(+) 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 a213189a3407499..af2b4e1cb66af91 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 f302da415d33f31..7f240fd60e717a9 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -2833,6 +2833,15 @@ def test_memoryview_tolist(self): m = memoryview(ex) self.assertRaises(NotImplementedError, m.tolist) + def test_memoryview_toreadonly(self): + a = array.array('h', list(range(-6, 6))) + m = memoryview(a) + self.assertFalse(m.readonly) + m = m.toreadonly() + self.assertTrue(m.readonly) + self.assertTrue(memoryview(m).readonly) + self.assertIs(m, m.toreadonly()) + def test_memoryview_repr(self): m = memoryview(bytearray(9)) r = m.__repr__() diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index ddd5b9a8e2f2505..89cc6173f64edd2 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -362,6 +362,26 @@ def test_reversed(self): self.assertEqual(list(reversed(m)), aslist) self.assertEqual(list(reversed(m)), list(m[::-1])) + def test_writable_toreadonly(self): + tp = self.rw_type + if tp is None: + self.skipTest("no writable type to test") + b = tp(self._source) + m = self._view(b) + self.assertFalse(m.readonly) + m = m.toreadonly() + self.assertTrue(m.readonly) + self.assertTrue(memoryview(m).readonly) + + def test_readonly_toreadonly(self): + tp = self.ro_type + if tp is None: + self.skipTest("no read-only type to test") + b = tp(self._source) + m = self._view(b) + self.assertTrue(m.readonly) + self.assertIs(m, m.toreadonly()) + 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 000000000000000..68785b3cb6a1d01 --- /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 ccf45ffc5826081..047f1709d07397f 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1398,6 +1398,21 @@ memory_cast(PyMemoryViewObject *self, PyObject *args, PyObject *kwds) return NULL; } +static PyObject * +memory_toreadonly(PyMemoryViewObject *self, PyObject *noargs) +{ + CHECK_RELEASED(self); + if (self->view.readonly) { + Py_INCREF(self); + return (PyObject *) self; + } + self = (PyMemoryViewObject *) mbuf_add_view(self->mbuf, &self->view); + if (self != NULL) { + self->view.readonly = 1; + }; + return (PyObject *) self; +} + /**************************************************************************/ /* getbuffer */ @@ -3061,6 +3076,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 +3087,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} From 4eca5acc1ee2b973e1e17d450285f407af84ff67 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sat, 14 Apr 2018 18:04:57 +0200 Subject: [PATCH 2/2] - Fix release() issue by always creating a new object - Streamline tests --- Lib/test/test_buffer.py | 48 ++++++++++++++++++------------------- Lib/test/test_memoryview.py | 29 ++++++++-------------- Objects/memoryobject.c | 7 +++--- 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 7f240fd60e717a9..a3f3ef098a6137b 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -924,23 +924,30 @@ def verify(self, result, obj=-1, 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) - rep = result.tolist() if fmt else result.tobytes() - self.assertEqual(rep, lst) - self.assertEqual(m, result) + 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)) + + 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): @@ -2833,15 +2840,6 @@ def test_memoryview_tolist(self): m = memoryview(ex) self.assertRaises(NotImplementedError, m.tolist) - def test_memoryview_toreadonly(self): - a = array.array('h', list(range(-6, 6))) - m = memoryview(a) - self.assertFalse(m.readonly) - m = m.toreadonly() - self.assertTrue(m.readonly) - self.assertTrue(memoryview(m).readonly) - self.assertIs(m, m.toreadonly()) - def test_memoryview_repr(self): m = memoryview(bytearray(9)) r = m.__repr__() diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 89cc6173f64edd2..ca307d8342f31f2 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -362,25 +362,16 @@ def test_reversed(self): self.assertEqual(list(reversed(m)), aslist) self.assertEqual(list(reversed(m)), list(m[::-1])) - def test_writable_toreadonly(self): - tp = self.rw_type - if tp is None: - self.skipTest("no writable type to test") - b = tp(self._source) - m = self._view(b) - self.assertFalse(m.readonly) - m = m.toreadonly() - self.assertTrue(m.readonly) - self.assertTrue(memoryview(m).readonly) - - def test_readonly_toreadonly(self): - tp = self.ro_type - if tp is None: - self.skipTest("no read-only type to test") - b = tp(self._source) - m = self._view(b) - self.assertTrue(m.readonly) - self.assertIs(m, m.toreadonly()) + 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]) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 047f1709d07397f..adaa67c064466bd 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1402,10 +1402,9 @@ static PyObject * memory_toreadonly(PyMemoryViewObject *self, PyObject *noargs) { CHECK_RELEASED(self); - if (self->view.readonly) { - Py_INCREF(self); - return (PyObject *) 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;