8000 Merge pull request #21029 from hawkinsp/nontuple_index · numpy/numpy@91d4973 · GitHub
[go: up one dir, main page]

Skip to content

Commit 91d4973

Browse files
authored
Merge pull request #21029 from hawkinsp/nontuple_index
DEP: Remove support for non-tuple nd-indices.
2 parents ee1722d + e352160 commit 91d4973

File tree

5 files changed

+26
-144
lines changed

5 files changed

+26
-144
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Expired deprecation of multidimensional indexing with non-tuple values
2+
----------------------------------------------------------------------
3+
4+
Multidimensional indexing with anything but a tuple was
5+
deprecated in NumPy 1.15.
6+
7+
Previously, code such as ``arr[ind]`` where ``ind = [[0, 1], [0, 1]]``
8+
produced a ``FutureWarning`` and was interpreted as a multidimensional
9+
index (i.e., ``arr[tuple(ind)]``). Now this example is treated like an
10+
array index over a single dimension (``arr[array(ind)]``).

doc/source/user/basics.indexing.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,6 @@ integer, or a tuple of slice objects and integers. :py:data:`Ellipsis`
104104
and :const:`newaxis` objects can be interspersed with these as
105105
well.
106106

107-
.. deprecated:: 1.15.0
108-
109-
In order to remain backward compatible with a common usage in
110-
Numeric, basic slicing is also initiated if the selection object is
111-
any non-ndarray and non-tuple sequence (such as a :class:`list`) containing
112-
:class:`slice` objects, the :py:data:`Ellipsis` object, or the :const:`newaxis`
113-
object, but not for integer arrays or other embedded sequences.
114-
115107
.. index::
116108
triple: ndarray; special methods; getitem
117109
triple: ndarray; special methods; setitem

numpy/core/src/multiarray/mapping.c

Lines changed: 10 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,8 @@ unpack_scalar(PyObject *index, PyObject **result, npy_intp NPY_UNUSED(result_n))
197197
/**
198198
* Turn an index argument into a c-array of `PyObject *`s, one for each index.
199199
*
200-
* When a scalar is passed, this is written directly to the buffer. When a
201-
* tuple is passed, the tuple elements are unpacked into the buffer.
202-
*
203-
* When some other sequence is passed, this implements the following section
204-
* from the advanced indexing docs to decide whether to unpack or just write
205-
* one element:
206-
*
207-
* > In order to remain backward compatible with a common usage in Numeric,
208-
* > basic slicing is also initiated if the selection object is any non-ndarray
209-
* > sequence (such as a list) containing slice objects, the Ellipsis object,
210-
* > or the newaxis object, but not for integer arrays or other embedded
211-
* > sequences.
212-
*
213-
* It might be worth deprecating this behaviour (gh-4434), in which case the
214-
* entire function should become a simple check of PyTuple_Check.
200+
* When a tuple is passed, the tuple elements are unpacked into the buffer.
201+
* Anything else is handled by unpack_scalar().
215202
*
216203
* @param index The index object, which may or may not be a tuple. This is
217204
* a borrowed reference.
@@ -228,129 +215,32 @@ unpack_scalar(PyObject *index, PyObject **result, npy_intp NPY_UNUSED(result_n))
228215
NPY_NO_EXPORT npy_intp
229216
unpack_indices(PyObject *index, PyObject **result, npy_intp result_n)
230217
{
231-
npy_intp n, i;
232-
npy_bool commit_to_unpack;
218+
/* It is likely that the logic here can be simplified. See the discussion
219+
* on https://github.com/numpy/numpy/pull/21029
220+
*/
233221

234222
/* Fast route for passing a tuple */
235223
if (PyTuple_CheckExact(index)) {
236224
return unpack_tuple((PyTupleObject *)index, result, result_n);
237225
}
238226

239-
/* Obvious single-entry cases */
240-
if (0 /* to aid macros below */
241-
|| PyLong_CheckExact(index)
242-
|| index == Py_None
243-
|| PySlice_Check(index)
244-
|| PyArray_Check(index)
245-
|| !PySequence_Check(index)
246-
|| PyUnicode_Check(index)) {
247-
248-
return unpack_scalar(index, result, result_n);
249-
}
250-
251227
/*
252228
* Passing a tuple subclass - coerce to the base type. This incurs an
253-
* allocation, but doesn't need to be a fast path anyway
229+
* allocation, but doesn't need to be a fast path anyway. Note that by
230+
* calling `PySequence_Tuple`, we ensure that the subclass `__iter__` is
231+
* called.
254232
*/
255233
if (PyTuple_Check(index)) {
256234
PyTupleObject *tup = (PyTupleObject *) PySequence_Tuple(index);
257235
if (tup == NULL) {
258236
return -1;
259237
}
260-
n = unpack_tuple(tup, result, result_n);
238+
npy_intp n = unpack_tuple(tup, result, result_n);
261239
Py_DECREF(tup);
262240
return n;
263241
}
264242

265-
/*
266-
* At this point, we're left with a non-tuple, non-array, sequence:
267-
* typically, a list. We use some somewhat-arbitrary heuristics from here
268-
* onwards to decided whether to treat that list as a single index, or a
269-
* list of indices.
270-
*/
271-
272-
/* if len fails, treat like a scalar */
273-
n = PySequence_Size(index);
274-
if (n < 0) {
275-
PyErr_Clear();
276-
return unpack_scalar(index, result, result_n);
277-
}
278-
279-
/*
280-
* Backwards compatibility only takes effect for short sequences - otherwise
281-
* we treat it like any other scalar.
282-
*
283-
* Sequences < NPY_MAXDIMS with any slice objects
284-
* or newaxis, Ellipsis or other arrays or sequences
285-
* embedded, are considered equivalent to an indexing
286-
* tuple. (`a[[[1,2], [3,4]]] == a[[1,2], [3,4]]`)
287-
*/
288-
if (n >= NPY_MAXDIMS) {
289-
return unpack_scalar(index, result, result_n);
290-
}
291-
292-
/* In case we change result_n elsewhere */
293-
assert(n <= result_n);
294-
295-
/*
296-
* Some other type of short sequence - assume we should unpack it like a
297-
* tuple, and then decide whether that was actually necessary.
298-
*/
299-
commit_to_unpack = 0;
300-
for (i = 0; i < n; i++) {
301-
PyObject *tmp_obj = result[i] = PySequence_GetItem(index, i);
302-
303-
if (commit_to_unpack) {
304-
/* propagate errors */
305-
if (tmp_obj == NULL) {
306-
goto fail;
307-
}
308-
}
309-
else {
310-
/*
311-
* if getitem fails (unusual) before we've committed, then stop
312-
* unpacking
313-
*/
314-
if (tmp_obj == NULL) {
315-
PyErr_Clear();
316-
break;
317-
}
318-
319-
/* decide if we should treat this sequence like a tuple */
320-
if (PyArray_Check(tmp_obj)
321-
|| PySequence_Check(tmp_obj)
322-
|| PySlice_Check(tmp_obj)
323-
|| tmp_obj == Py_Ellipsis
324-
|| tmp_obj == Py_None) {
325-
if (DEPRECATE_FUTUREWARNING(
326-
"Using a non-tuple sequence for multidimensional "
327-
"indexing is deprecated; use `arr[tuple(seq)]` "
328-
"instead of `arr[seq]`. In the future this will be "
329-
"interpreted as an array index, `arr[np.array(seq)]`, "
330-
"which will result either in an error or a different "
331-
"result.") < 0) {
332-
i++; /* since loop update doesn't run */
333-
goto fail;
334-
}
335-
commit_to_unpack = 1;
336-
}
337-
}
338-
}
339-
340-
/* unpacking was the right thing to do, and we already did it */
341-
if (commit_to_unpack) {
342-
return n;
343-
}
344-
/* got to the end, never found an indication that we should have unpacked */
345-
else {
346-
/* we partially filled result, so empty it first */
347-
multi_DECREF(result, i);
348-
return unpack_scalar(index, result, result_n);
349-
}
350-
351-
fail:
352-
multi_DECREF(result, i);
353-
return -1;
243+
return unpack_scalar(index, result, result_n);
354244
}
355245

356246
/**

numpy/core/tests/test_deprecations.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,6 @@ class _VisibleDeprecationTestCase(_DeprecationTestCase):
138138
warning_cls = np.VisibleDeprecationWarning
139139

140140

141-
class TestNonTupleNDIndexDeprecation:
142-
def test_basic(self):
143-
a = np.zeros((5, 5))
144-
with warnings.catch_warnings():
145-
warnings.filterwarnings('always')
146-
assert_warns(FutureWarning, a.__getitem__, [[0, 1], [0, 1]])
147-
assert_warns(FutureWarning, a.__getitem__, [slice(None)])
148-
149-
warnings.filterwarnings('error')
150-
assert_raises(FutureWarning, a.__getitem__, [[0, 1], [0, 1]])
151-
assert_raises(FutureWarning, a.__getitem__, [slice(None)])
152-
153-
# a a[[0, 1]] always was advanced indexing, so no error/warning
154-
a[[0, 1]]
155-
156-
157141
class TestComparisonDeprecations(_DeprecationTestCase):
158142
"""This tests the deprecation, for non-element-wise comparison logic.
159143
This used to mean that when an error occurred during element-wise comparison

numpy/core/tests/test_indexing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,12 @@ def func(arr):
587587

588588
assert arr.dtype is dt
589589

590+
def test_nontuple_ndindex(self):
591+
a = np.arange(25).reshape((5, 5))
592+
assert_equal(a[[0, 1]], np.array([a[0], a[1]]))
593+
assert_equal(a[[0, 1], [0, 1]], np.array([0, 6]))
594+
assert_raises(IndexError, a.__getitem__, [slice(None)])
595+
590596

591597
class TestFieldIndexing:
592598
def test_scalar_return_type(self):

0 commit comments

Comments
 (0)
0