From c24e43c0eb4dd6f9e4bf12519000cb9f662a5425 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 8 Jul 2019 13:18:44 -0700 Subject: [PATCH] DEP: Speed up WarnOnWrite deprecation in buffer interface When a buffer interface does not request a writeable buffer, simply pass a read-only one when the warn on write flag is set. This is to give an easier way forward with avoiding the deprecation warnings: Simply do not ask for a writeable buffer. It will break code that expects writeable buffers but does not ask for them specifically a bit harder than would be nice. But since such code probably should ask for it specifically, this is likely fine (an RC release has to find out). The main reason for this is, that this way it plays very will with cython, which requests writeable buffers explicitly and if declared `const` is happy about read-only (so that using `const` is the best way to avoid the warning and makes code cleaner). Closes gh-13929, gh-13974 --- doc/release/1.17.0-notes.rst | 6 ++++++ numpy/core/src/multiarray/buffer.c | 23 ++++++++++----------- numpy/lib/tests/test_stride_tricks.py | 29 ++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/doc/release/1.17.0-notes.rst b/doc/release/1.17.0-notes.rst index 23a56ffe330e..edc1d2237f69 100644 --- a/doc/release/1.17.0-notes.rst +++ b/doc/release/1.17.0-notes.rst @@ -91,6 +91,12 @@ produce the deprecation warning. To help alleviate confusion, an additional `FutureWarning` will be emitted when accessing the ``writeable`` flag state to clarify the contradiction. +Note that for the C-side buffer protocol such an array will return a +readonly buffer immediately unless a writable buffer is requested. If +a writeable buffer is requested a warning will be given. When using +cython, the ``const`` qualifier should be used with such arrays to avoid +the warning (e.g. ``cdef const double[::1] view``). + Future Changes ============== diff --git a/numpy/core/src/multiarray/buffer.c b/numpy/core/src/multiarray/buffer.c index d8ad80266431..b729027ad4be 100644 --- a/numpy/core/src/multiarray/buffer.c +++ b/numpy/core/src/multiarray/buffer.c @@ -771,17 +771,6 @@ array_getbuffer(PyObject *obj, Py_buffer *view, int flags) goto fail; } } - /* - * If a read-only buffer is requested on a read-write array, we return a - * read-write buffer, which is dubious behavior. But that's why this call - * is guarded by PyArray_ISWRITEABLE rather than (flags & - * PyBUF_WRITEABLE). - */ - if (PyArray_ISWRITEABLE(self)) { - if (array_might_be_written(self) < 0) { - goto fail; - } - } if (view == NULL) { PyErr_SetString(PyExc_ValueError, "NULL view in getbuffer"); @@ -797,7 +786,17 @@ array_getbuffer(PyObject *obj, Py_buffer *view, int flags) view->buf = PyArray_DATA(self); view->suboffsets = NULL; view->itemsize = PyArray_ITEMSIZE(self); - view->readonly = !PyArray_ISWRITEABLE(self); + /* + * If a read-only buffer is requested on a read-write array, we return a + * read-write buffer as per buffer protocol. + * We set a requested buffer to readonly also if the array will be readonly + * after a deprecation. This jumps the deprecation, but avoiding the + * warning is not convenient here. A warning is given if a writeable + * buffer is requested since `PyArray_FailUnlessWriteable` is called above + * (and clears the `NPY_ARRAY_WARN_ON_WRITE` flag). + */ + view->readonly = (!PyArray_ISWRITEABLE(self) || + PyArray_CHKFLAGS(self, NPY_ARRAY_WARN_ON_WRITE)); view->internal = NULL; view->len = PyArray_NBYTES(self); if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) { diff --git a/numpy/lib/tests/test_stride_tricks.py b/numpy/lib/tests/test_stride_tricks.py index 955fb914c090..85fcceedc1e3 100644 --- a/numpy/lib/tests/test_stride_tricks.py +++ b/numpy/lib/tests/test_stride_tricks.py @@ -417,17 +417,21 @@ def test_writeable(): # but the result of broadcast_arrays needs to be writeable, to # preserve backwards compatibility - for results in [broadcast_arrays(original), - broadcast_arrays(0, original)]: + for is_broadcast, results in [(False, broadcast_arrays(original,)), + (True, broadcast_arrays(0, original))]: for result in results: # This will change to False in a future version - if any([s == 0 for s in result.strides]): + if is_broadcast: with assert_warns(FutureWarning): assert_equal(result.flags.writeable, True) with assert_warns(DeprecationWarning): result[:] = 0 # Warning not emitted, writing to the array resets it assert_equal(result.flags.writeable, True) + else: + # No warning: + assert_equal(result.flags.writeable, True) + for results in [broadcast_arrays(original), broadcast_arrays(0, original)]: for result in results: @@ -451,6 +455,25 @@ def test_writeable(): assert_(first.shape == second.shape) +def test_writeable_memoryview(): + # The result of broadcast_arrays exports as a non-writeable memoryview + # because otherwise there is no good way to opt in to the new behaviour + # (i.e. you would need to set writeable to False explicitly). + # See gh-13929. + original = np.array([1, 2, 3]) + + for is_broadcast, results in [(False, broadcast_arrays(original,)), + (True, broadcast_arrays(0, original))]: + for result in results: + # This will change to False in a future version + if is_broadcast: + # memoryview(result, writable=True) will give warning but cannot + # be tested using the python API. + assert memoryview(result).readonly + else: + assert not memoryview(result).readonly + + def test_reference_types(): input_array = np.array('a', dtype=object) expected = np.array(['a'] * 3, dtype=object)