Skip to content

Commit fd6cfd6

Browse files
committed
ENH: Make the ndarray diagonal method return a view.
Also remove the test_diagonal_deprecation test and add test that checks that a view is returned and that it is not writeable. Closes #596.
1 parent 9464075 commit fd6cfd6

File tree

4 files changed

+31
-134
lines changed

4 files changed

+31
-134
lines changed

numpy/add_newdocs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3316,7 +3316,9 @@ def luf(lamdaexpr, *args, **kwargs):
33163316
"""
33173317
a.diagonal(offset=0, axis1=0, axis2=1)
33183318
3319-
Return specified diagonals.
3319+
Return specified diagonals. In NumPy 1.9 the returned array is a
3320+
read-only view instead of a copy as in previous NumPy versions. In
3321+
NumPy 1.10 the read-only restriction will be removed.
33203322
33213323
Refer to :func:`numpy.diagonal` for full documentation.
33223324

numpy/core/fromnumeric.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,16 +1125,15 @@ def diagonal(a, offset=0, axis1=0, axis2=1):
11251125
In versions of NumPy prior to 1.7, this function always returned a new,
11261126
independent array containing a copy of the values in the diagonal.
11271127
1128-
In NumPy 1.7, it continues to return a copy of the diagonal, but depending
1129-
on this fact is deprecated. Writing to the resulting array continues to
1130-
work as it used to, but a FutureWarning will be issued.
1128+
In NumPy 1.7 and 1.8, it continues to return a copy of the diagonal,
1129+
but depending on this fact is deprecated. Writing to the resulting
1130+
array continues to work as it used to, but a FutureWarning is issued.
11311131
1132-
In NumPy 1.9, it will switch to returning a read-only view on the original
1133-
array. Attempting to write to the resulting array will produce an error.
1132+
In NumPy 1.9 it returns a read-only view on the original array.
1133+
Attempting to write to the resulting array will produce an error.
11341134
1135-
In NumPy 1.10, it will still return a view, but this view will no longer be
1136-
marked read-only. Writing to the returned array will alter your original
1137-
array as well.
1135+
In NumPy 1.10, it will return a read/write view, Writing to the returned
1136+
array will alter your original array.
11381137
11391138
If you don't write to the array returned by this function, then you can
11401139
just ignore all of the above.

numpy/core/src/multiarray/item_selection.c

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2341,14 +2341,13 @@ PyArray_Diagonal(PyArrayObject *self, int offset, int axis1, int axis2)
23412341
return NULL;
23422342
}
23432343

2344-
/* For backwards compatibility, during the deprecation period: */
2345-
copy = PyArray_NewCopy(ret, NPY_KEEPORDER);
2346-
Py_DECREF(ret);
2347-
if (!copy) {
2348-
return NULL;
2349-
}
2350-
PyArray_ENABLEFLAGS((PyArrayObject *)copy, NPY_ARRAY_WARN_ON_WRITE);
2351-
return copy;
2344+
/*
2345+
* For numpy 1.9 the diagonal view is not writeable.
2346+
* This line needs to be removed in 1.10.
2347+
*/
2348+
PyArray_CLEARFLAGS(ret, NPY_ARRAY_WRITEABLE);
2349+
2350+
return ret;
23522351
}
23532352

23542353
/*NUMPY_API

numpy/core/tests/test_multiarray.py

Lines changed: 14 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,123 +1343,20 @@ def test_diagonal(self):
13431343
# Order of axis argument doesn't matter:
13441344
assert_equal(b.diagonal(0, 2, 1), [[0, 3], [4, 7]])
13451345

1346-
def test_diagonal_deprecation(self):
1347-
1348-
def collect_warning_types(f, *args, **kwargs):
1349-
with warnings.catch_warnings(record=True) as log:
1350-
warnings.simplefilter("always")
1351-
f(*args, **kwargs)
1352-
return [w.category for w in log]
1353-
1354-
a = np.arange(9).reshape(3, 3)
1355-
# All the different functions raise a warning, but not an error, and
1356-
# 'a' is not modified:
1357-
assert_equal(collect_warning_types(a.diagonal().__setitem__, 0, 10),
1358-
[FutureWarning])
1359-
assert_equal(a, np.arange(9).reshape(3, 3))
1360-
assert_equal(collect_warning_types(np.diagonal(a).__setitem__, 0, 10),
1361-
[FutureWarning])
1362-
assert_equal(a, np.arange(9).reshape(3, 3))
1363-
assert_equal(collect_warning_types(np.diag(a).__setitem__, 0, 10),
1364-
[FutureWarning])
1365-
assert_equal(a, np.arange(9).reshape(3, 3))
1366-
# Views also warn
1367-
d = np.diagonal(a)
1368-
d_view = d.view()
1369-
assert_equal(collect_warning_types(d_view.__setitem__, 0, 10),
1370-
[FutureWarning])
1371-
# But the write goes through:
1372-
assert_equal(d[0], 10)
1373-
# Only one warning per call to diagonal, though (even if there are
1374-
# multiple views involved):
1375-
assert_equal(collect_warning_types(d.__setitem__, 0, 10),
1376-
[])
1377-
1378-
# Other ways of accessing the data also warn:
1379-
# .data goes via the C buffer API, gives a read-write
1380-
# buffer/memoryview. We don't warn until tp_getwritebuf is actually
1381-
# called, which is not until the buffer is written to.
1382-
have_memoryview = (hasattr(__builtins__, "memoryview")
1383-
or "memoryview" in __builtins__)
1384-
def get_data_and_write(getter):
1385-
buf_or_memoryview = getter(a.diagonal())
1386-
if (have_memoryview and isinstance(buf_or_memoryview, memoryview)):
1387-
buf_or_memoryview[0] = np.array(1)
1388-
else:
1389-
buf_or_memoryview[0] = "x"
1390-
assert_equal(collect_warning_types(get_data_and_write,
1391-
lambda d: d.data),
1392-
[FutureWarning])
1393-
if hasattr(np, "getbuffer"):
1394-
assert_equal(collect_warning_types(get_data_and_write,
1395-
np.getbuffer),
1396-
[FutureWarning])
1397-
# PEP 3118:
1398-
if have_memoryview:
1399-
assert_equal(collect_warning_types(get_data_and_write, memoryview),
1400-
[FutureWarning])
1401-
# Void dtypes can give us a read-write buffer, but only in Python 2:
1402-
import sys
1403-
if sys.version_info[0] < 3:
1404-
aV = np.empty((3, 3), dtype="V10")
1405-
assert_equal(collect_warning_types(aV.diagonal().item, 0),
1406-
[FutureWarning])
1407-
# XX it seems that direct indexing of a void object returns a void
1408-
# scalar, which ignores not just WARN_ON_WRITE but even WRITEABLE.
1409-
# i.e. in this:
1410-
# a = np.empty(10, dtype="V10")
1411-
# a.flags.writeable = False
1412-
# buf = a[0].item()
1413-
# 'buf' ends up as a writeable buffer. I guess no-one actually
1414-
# uses void types like this though...
1415-
# __array_interface also lets a data pointer get away from us
1416-
log = collect_warning_types(getattr, a.diagonal(),
1417-
"__array_interface__")
1418-
assert_equal(log, [FutureWarning])
1419-
# ctypeslib goes via __array_interface__:
1420-
try:
1421-
# may not exist in python 2.4:
1422-
import ctypes
1423-
except ImportError:
1424-
pass
1425-
else:
1426-
log = collect_warning_types(np.ctypeslib.as_ctypes, a.diagonal())
1427-
assert_equal(log, [FutureWarning])
1428-
# __array_struct__
1429-
log = collect_warning_types(getattr, a.diagonal(), "__array_struct__")
1430-
assert_equal(log, [FutureWarning])
1431-
1432-
# Make sure that our recommendation to silence the warning by copying
1433-
# the array actually works:
1434-
diag_copy = a.diagonal().copy()
1435-
assert_equal(collect_warning_types(diag_copy.__setitem__, 0, 10),
1436-
[])
1437-
# There might be people who get a spurious warning because they are
1438-
# extracting a buffer, but then use that buffer in a read-only
1439-
# fashion. And they might get cranky at having to create a superfluous
1440-
# copy just to work around this spurious warning. A reasonable
1441-
# solution would be for them to mark their usage as read-only, and
1442-
# thus safe for both past and future PyArray_Diagonal
1443-
# semantics. So let's make sure that setting the diagonal array to
1444-
# non-writeable will suppress these warnings:
1445-
ro_diag = a.diagonal()
1446-
ro_diag.flags.writeable = False
1447-
assert_equal(collect_warning_types(getattr, ro_diag, "data"), [])
1448-
# __array_interface__ has no way to communicate read-onlyness --
1449-
# effectively all __array_interface__ arrays are assumed to be
1450-
# writeable :-(
1451-
# ro_diag = a.diagonal()
1452-
# ro_diag.flags.writeable = False
1453-
# assert_equal(collect_warning_types(getattr, ro_diag,
1454-
# "__array_interface__"), [])
1455-
if hasattr(__builtins__, "memoryview"):
1456-
ro_diag = a.diagonal()
1457-
ro_diag.flags.writeable = False
1458-
assert_equal(collect_warning_types(memoryview, ro_diag), [])
1459-
ro_diag = a.diagonal()
1460-
ro_diag.flags.writeable = False
1461-
assert_equal(collect_warning_types(getattr, ro_diag,
1462-
"__array_struct__"), [])
1346+
def test_diagonal_view_notwriteable(self):
1347+
# this test is only for 1.9, the diagonal view will be
1348+
# writeable in 1.10.
1349+
a = np.eye(3).diagonal()
1350+
assert_(not a.flags.writeable)
1351+
assert_(not a.flags.owndata)
1352+
1353+
a = np.diagonal(np.eye(3))
1354+
assert_(not a.flags.writeable)
1355+
assert_(not a.flags.owndata)
1356+
1357+
a = np.diag(np.eye(3))
1358+
assert_(not a.flags.writeable)
1359+
assert_(not a.flags.owndata)
14631360

14641361
def test_diagonal_memleak(self):
14651362
# Regression test for a bug that crept in at one point

0 commit comments

Comments
 (0)