bpo-33176 Add readonly mode to memoryviews#6314
Conversation
|
CC: @pitrou This is an initial working implementation of the feature to discuss the design and other aspects. |
pitrou
left a comment
There was a problem hiding this comment.
Thanks for doing this. I've posted a couple of comments below. You'll also need to update the memoryview.cast documentation in Doc/library/stdtypes.rst.
There was a problem hiding this comment.
The Py_buffer structure already a readonly field, so you don't need to add this. See https://docs.python.org/3/c-api/buffer.html#buffer-structure
There was a problem hiding this comment.
Even if it's not writable, you should still be able to cast to readonly=True.
There was a problem hiding this comment.
Should I test both (writable and not writable) separately?
There was a problem hiding this comment.
I did that adding a explicit test for readonly=False and readonly buffer. (Raises when casting a readonly buffer to a writable one).
There was a problem hiding this comment.
I wonder if it makes sense for the readonly argument to be keyword-only. @skrah may have an opinion on this.
There was a problem hiding this comment.
Yes, intuitively I think it should be keyword-only.
There was a problem hiding this comment.
Instead of this, just use the p type format in PyArgs_ParseTuple above (see https://docs.python.org/3/c-api/arg.html#other-objects).
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
|
I agree with all of Antoine's comments. The flag should just set |
|
I have made the requested changes; please review again |
|
Thanks for making the requested changes! @pitrou: please review the changes made to this pull request. |
pitrou
left a comment
There was a problem hiding this comment.
Thanks for the updates. I have some more minor comments (see below). However, I noticed a more fundamental limitation that I think would deserve fixing for this new feature to be really useful. I'll post separately about it.
| .. versionadded:: 3.2 | ||
|
|
||
| .. method:: cast(format[, shape]) | ||
| .. method:: cast(format[, shape, readonly]) |
There was a problem hiding this comment.
The signature here should be cast(format[, shape[, readonly]]) (note the additional square brackets around the "readonly" parameter, since it's optional).
There was a problem hiding this comment.
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.
| 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 | ||
| a writable view will raise a :class:`ValueError`. |
There was a problem hiding this comment.
Judging by the code, it's TypeError not ValueError.
There was a problem hiding this comment.
Also you'll need to add a versionchanged entry below, for the added parameter.
| m = None | ||
| self.assertEqual(sys.getrefcount(b), oldrefcount) | ||
|
|
||
| def test_setitem_readonly_and_writable_buffer(self): |
There was a problem hiding this comment.
Nit: I would call this test test_cast_writable_to_readonly, or something.
| b = self.rw_type(self._source) | ||
| oldrefcount = sys.getrefcount(b) | ||
| m = self._view(b) | ||
| readonly_m = m.cast("B", readonly=True) |
There was a problem hiding this comment.
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.
| readonly_m = m.cast("B", readonly=True) | ||
| def setitem(value): | ||
| readonly_m[0] = value | ||
| self.assertRaises(TypeError, setitem, 12) |
There was a problem hiding this comment.
Also check that readonly_m.readonly is True?
Same for the other test below.
| readonly_m = None | ||
| self.assertEqual(sys.getrefcount(b), oldrefcount) | ||
|
|
||
| def test_setitem_readonly_and_readonly_buffer(self): |
There was a problem hiding this comment.
Same as above: rename this test_cast_readonly_to_readonly?
|
|
||
| def test_setitem_readonly_and_readonly_buffer(self): | ||
| if not self.ro_type: | ||
| self.skipTest("no writable type to test") |
There was a problem hiding this comment.
You mean "no readonly type to test".
|
|
||
| if (self->view.readonly && !readonly){ | ||
| PyErr_SetString(PyExc_TypeError, | ||
| "memoryview: cannot cast readonly buffer to a writable buffer."); |
|
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase |
|
As I mentioned in my review above, there is a fundamental limitation that this PR is hitting, and it's that currently To witness it without Numpy installed, CPython has a private type And the issue is similar if using non-byte formats and trying to cast to an identical format: For this PR to be truly useful, |
|
@pitrou Should we close this PR then? |
|
I don't think so. I think that supporting the case where |
|
@pitrou On the contrary, I'm more than happy to continue! Just wanted to understand what is the status of this PR. Thanks! |
|
Perhaps we can make I'm not sure about our policy of making a required parameter not-required. |
|
I think it's ok to make |
|
I think we can't change the behavior that Initially the point of Allowing the same shape would be one way out, but I'm not complete happy with writing Perhaps
|
|
While |
| .. versionadded:: 3.2 | ||
|
|
||
| .. method:: cast(format[, shape]) | ||
| .. method:: cast(format[, shape, readonly]) |
There was a problem hiding this comment.
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.
| 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 |
| 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 | ||
| a writable view will raise a :class:`ValueError`. |
There was a problem hiding this comment.
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?
|
Okay, let's use Regarding Martin's points: Probably While we're at it, |
|
It strikes me that perhaps we're asking too much to the |
|
I like that, probably converting to readonly will be used separately from casting most of the time anyway.
|
|
See PR #6466 for the |
|
@pablogsal, yes, I think so. Sorry for leading you into a dead end :-) |
|
@pitrou No problem at all :) |
https://bugs.python.org/issue33176