8000 ENH: Make the ndarray diagonal method return a view. · numpy/numpy@fd6cfd6 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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_ F438 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)
0