Skip to content

bpo-33176 Add readonly mode to memoryviews #6314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3624,13 +3624,15 @@ copying.

.. versionadded:: 3.2

.. method:: cast(format[, shape])
.. method:: cast(format[, shape, readonly])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature here should be cast(format[, shape[, readonly]]) (note the additional square brackets around the "readonly" parameter, since it's optional).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like

cast(format[, shape], *, readonly=<unchanged>)

That implies readonly is a keyword-only parameter, which matches your doc string and tests. Using the keyword notation also reduces the bracket nesting.


Cast a memoryview to a new format or shape. *shape* defaults to
``[byte_length//new_itemsize]``, which means that the result view
will be one-dimensional. The return value is a new memoryview, but
the buffer itself is not copied. Supported casts are 1D -> C-:term:`contiguous`
and C-contiguous -> 1D.
and C-contiguous -> 1D. If *readonly* is ``True`` the result view will
not be writable. Triying to cast a view with a readonly underlying buffer to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: Trying

a writable view will raise a :class:`ValueError`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by the code, it's TypeError not ValueError.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you'll need to add a versionchanged entry below, for the added parameter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does readonly=False mean, and what is the default behaviour? How do I avoid casting a readonly view to writable? Will I have to add readonly=True to all my casts?


The destination format is restricted to a single element native format in
:mod:`struct` syntax. One of the formats must be a byte format
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@ def setitem(value):
m = None
self.assertEqual(sys.getrefcount(b), oldrefcount)

def test_setitem_readonly_and_writable_buffer(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would call this test test_cast_writable_to_readonly, or something.

if not self.rw_type:
self.skipTest("no writable type to test")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it's not writable, you should still be able to cast to readonly=True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I test both (writable and not writable) separately?

Copy link
Member Author

@pablogsal pablogsal Mar 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that adding a explicit test for readonly=False and readonly buffer. (Raises when casting a readonly buffer to a writable one).

b = self.rw_type(self._source)
oldrefcount = sys.getrefcount(b)
m = self._view(b)
readonly_m = m.cast("B", readonly=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would test m.cast(m.format, readonly=True) instead, to make sure that only changing writability is supported.

Same for the other test below.

def setitem(value):
readonly_m[0] = value
self.assertRaises(TypeError, setitem, 12)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also check that readonly_m.readonly is True?

Same for the other test below.

m = None
readonly_m = None
self.assertEqual(sys.getrefcount(b), oldrefcount)

def test_setitem_readonly_and_readonly_buffer(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: rename this test_cast_readonly_to_readonly?

if not self.ro_type:
self.skipTest("no writable type to test")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean "no readonly type to test".

tp = self.ro_type
b = self.ro_type(self._source)
oldrefcount = sys.getrefcount(b)
m = self._view(b)
self.assertRaises(TypeError, lambda: m.cast("B", readonly=False))
readonly_m = m.cast("B", readonly=True)
def setitem(value):
readonly_m[0] = value
self.assertRaises(TypeError, setitem, 12)
m = None
readonly_m = None
self.assertEqual(sys.getrefcount(b), oldrefcount)

def test_setitem_writable(self):
if not self.rw_type:
self.skipTest("no writable type to test")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add readonly flag to memoryview.cast to create readonly memoryviews.
18 changes: 14 additions & 4 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1338,16 +1338,17 @@ zero_in_shape(PyMemoryViewObject *mv)
static PyObject *
memory_cast(PyMemoryViewObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"format", "shape", NULL};
static char *kwlist[] = {"format", "shape", "readonly", NULL};
PyMemoryViewObject *mv = NULL;
PyObject *shape = NULL;
int readonly = self->view.readonly;
PyObject *format;
Py_ssize_t ndim = 1;

CHECK_RELEASED(self);

if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist,
&format, &shape)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O$p", kwlist,
&format, &shape, &readonly)) {
return NULL;
}
if (!PyUnicode_Check(format)) {
Expand All @@ -1365,6 +1366,13 @@ memory_cast(PyMemoryViewObject *self, PyObject *args, PyObject *kwds)
"memoryview: cannot cast view with zeros in shape or strides");
return NULL;
}

if (self->view.readonly && !readonly){
PyErr_SetString(PyExc_TypeError,
"memoryview: cannot cast readonly buffer to a writable buffer.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this line needs indenting.

return NULL;
}

if (shape) {
CHECK_LIST_OR_TUPLE(shape)
ndim = PySequence_Fast_GET_SIZE(shape);
Expand All @@ -1391,6 +1399,8 @@ memory_cast(PyMemoryViewObject *self, PyObject *args, PyObject *kwds)
if (shape && cast_to_ND(mv, shape, (int)ndim) < 0)
goto error;

mv->view.readonly = readonly;

return (PyObject *)mv;

error:
Expand Down Expand Up @@ -3058,7 +3068,7 @@ PyDoc_STRVAR(memory_tolist_doc,
\n\
Return the data in the buffer as a list of elements.");
PyDoc_STRVAR(memory_cast_doc,
"cast($self, /, format, *, shape)\n--\n\
"cast($self, /, format, *, shape, readonly)\n--\n\
\n\
Cast a memoryview to a new format or shape.");

Expand Down