8000 DEP: Deprecate non-tuple multidimensional indices · numpy/numpy@81aa766 · GitHub
[go: up one dir, main page]

Skip to content

Commit 81aa766

Browse files
committed
DEP: Deprecate non-tuple multidimensional indices
Such indexing has problem with ambiguouity for example whether `arr[[[0], [1]]]` is `arr[[0], [1]]` or `arr[asarray([[0], [1]])]` Adds the tuple cast where it is necessary (at the time of writing this, it is not unlikely that there are some hidden ones missing).
1 parent d05d26a commit 81aa766

File tree

9 files changed

+120
-41
lines changed

9 files changed

+120
-41
lines changed

doc/release/1.11.0-notes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ DeprecationWarning to error
3939
FutureWarning to changed behavior
4040
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4141

42+
* Multidimensional indexing with anything but a base class tuple is
43+
deprecated. This means that code such as ``arr[[slice(None)]]`` has to
44+
be changed to ``arr[tuple([slice(None)])]``. This is necessary to avoid
45+
the ambiguity of expressions such as ``arr[[[0, 1], [0, 1]]]`` which
46+
currently are interpreted as ``arr[[0, 1], [0, 1]]`` using heuristics.
47+
Future or deprecation warnings are given depending on the index.
48+
4249
* In ``np.lib.split`` an empty array in the result always had dimension
4350
``(0,)`` no matter the dimensions of the array being split. This
4451
has been changed so that the dimensions will be preserved. A

numpy/core/src/multiarray/mapping.c

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ prepare_index(PyArrayObject *self, PyObject *index,
200200
* or newaxis, Ellipsis or other arrays or sequences
201201
* embedded, are considered equivalent to an indexing
202202
* tuple. (`a[[[1,2], [3,4]]] == a[[1,2], [3,4]]`)
203+
*
204+
* Use make_tuple = 1 to denote a non-base class tuple
205+
* Use make_tuple = 2 for indices that could be valid non-converted
206+
* use make_tuple = 3 to denote a conversion that will be an error
203207
*/
204208

205209
if (PyTuple_Check(index)) {
@@ -221,18 +225,49 @@ prepare_index(PyArrayObject *self, PyObject *index,
221225
make_tuple = 0;
222226
break;
223227
}
224-
if (PyArray_Check(tmp_obj) || PySequence_Check(tmp_obj)
225-
|| PySlice_Check(tmp_obj) || tmp_obj == Py_Ellipsis
226-
|| tmp_obj == Py_None) {
227-
make_tuple = 1;
228+
if (PyArray_Check(tmp_obj) || PySequence_Check(tmp_obj)) {
229+
make_tuple = 2;
230+
}
231+
else if (PySlice_Check(tmp_obj) || (tmp_obj == Py_Ellipsis) ||
232+
(tmp_obj == Py_None)) {
233+
make_tuple = 3;
228234
Py_DECREF(tmp_obj);
229235
break;
230-
}
236+
}
231237
Py_DECREF(tmp_obj);
232238
}
233239

234240
if (make_tuple) {
235241
/* We want to interpret it as a tuple, so make it one */
242+
if (make_tuple == 2) {
243+
if (DEPRECATE_FUTUREWARNING(
244+
"using a non-tuple sequence for multidimensional "
245+
"indexing is deprecated; use `arr[tuple(seq)]` "
246+
"instead of `arr[seq]`. In the future this will be "
247+
"interpreted as an array index.") < 0) {
248+
return -1;
249+
}
250+
}
251+
else if (make_tuple == 1) {
252+
if (DEPRECATE_FUTUREWARNING(
253+
"multi dimensional indexing tuple was not base class. "
254+
"Using a non-tuple sequence for multidimensional "
255+
"indexing is deprecated, use `arr[tuple(seq)]` "
256+
"instead of `arr[seq]`. In the future this will be "
257+
"interpreted as an array index.") < 0) {
258+
return -1;
259+
}
260+
}
261+
else {
262+
if (DEPRECATE(
263+
"using a non-tuple sequence for multidimensional "
264+
"indexing is deprecated; use `arr[tuple(seq)]` "
265+
"instead of `arr[seq]`. In the future this indexing "
266+
"operation will be an error.") < 0) {
267+
return -1;
268+
}
269+
}
270+
236271
index = PySequence_Tuple(index);
237272
if (index == NULL) {
238273
return -1;

numpy/core/tests/test_deprecations.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,33 @@ def test_operator_deprecation(self):
152152
self.assert_deprecated(operator.sub, args=(generic, generic))
153153

154154

155+
class TestNonTupleNDIndexDeprecation(object):
156+
def test_basic(self):
157+
a = np.zeros((5, 5))
158+
with warnings.catch_warnings():
159+
warnings.filterwarnings('always')
160+
assert_warns(FutureWarning, a.__getitem__, [[0, 1], [0, 1]])
161+
assert_warns(DeprecationWarning, a.__getitem__, [slice(None)])
162+
163+
warnings.filterwarnings('error')
164+
assert_raises(FutureWarning, a.__getitem__, [[0, 1], [0, 1]])
165+
assert_raises(DeprecationWarning, a.__getitem__, [slice(None)])
166+
167+
# a a[[0, 1]] always was advanced indexing, so no error/warning
168+
a[[0, 1]]
169+
170+
def test_not_exact_tuple(self):
171+
a = np.zeros((5, 5))
172+
class TupleSubclass(tuple):
173+
pass
174+
175+
with warnings.catch_warnings():
176+
warnings.filterwarnings('always')
177+
assert_warns(FutureWarning, a.__getitem__, TupleSubclass((1, 2)))
178+
warnings.filterwarnings('error')
179+
assert_raises(FutureWarning, a.__getitem__, TupleSubclass((1, 2)))
180+
181+
155182
class TestRankDeprecation(_DeprecationTestCase):
156183
"""Test that np.rank is deprecated. The function should simply be
157184
removed. The VisibleDeprecationWarning may become unnecessary.

numpy/core/tests/test_memmap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_arithmetic_drops_references(self):
108108
def test_indexing_drops_references(self):
109109
fp = memmap(self.tmpfp, dtype=self.dtype, mode='w+',
110110
shape=self.shape)
111-
tmp = fp[[(1, 2), (2, 3)]]
111+
tmp = fp[((1, 2), (2, 3))]
112112
if isinstance(tmp, memmap):
113113
assert tmp._mmap is not fp._mmap
114114

numpy/fft/fftpack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ def _raw_fft(a, n=None, axis=-1, init_function=fftpack.cffti,
6767
if s[axis] > n:
6868
index = [slice(None)]*len(s)
6969
index[axis] = slice(0, n)
70-
a = a[index]
70+
a = a[tuple(index)]
7171
else:
7272
index = [slice(None)]*len(s)
7373
index[axis] = slice(0, s[axis])
7474
s[axis] = n
7575
z = zeros(s, a.dtype.char)
76-
z[index] = a
76+
z[tuple(index)] = a
7777
a = z
7878

7979
if axis != -1:

numpy/lib/arraypad.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,9 +1363,9 @@ def pad(array, pad_width, mode, **kwargs):
13631363
# Create a new padded array
13641364
rank = list(range(len(narray.shape)))
13651365
total_dim_increase = [np.sum(pad_width[i]) for i in rank]
1366-
offset_slices = [slice(pad_width[i][0],
1367-
pad_width[i][0] + narray.shape[i])
1368-
for i in rank]
1366+
offset_slices = tuple(
1367+
slice(pad_width[i][0], pad_width[i][0] + narray.shape[i])
1368+
for i in rank)
13691369
new_shape = np.array(narray.shape) + total_dim_increase
13701370
newmat = np.zeros(new_shape, narray.dtype)
13711371

numpy/lib/function_base.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ def histogramdd(sample, bins=10, range=None, normed=False, weights=None):
647647
ni[i], ni[j] = ni[j], ni[i]
648648

649649
# Remove outliers (indices 0 and -1 for each dimension).
650-
core = D*[slice(1, -1)]
650+
core = D * (slice(1, -1),)
651651
hist = hist[core]
652652

653653
# Normalize if normed is True
@@ -1242,11 +1242,11 @@ def gradient(f, *varargs, **kwargs):
12421242
# Needs to keep the specific units, can't be a general unit
12431243
otype = f.dtype
12441244

1245-
# Convert datetime64 data into ints. Make dummy variable `y`
1246-
# that is a view of ints if the data is datetime64, otherwise
1245+
# Convert datetime64 data into timedelta. Make dummy variable `y`
1246+
# that is a view of timedelta if the data is datetime64, otherwise
12471247
# just set y equal to the the array `f`.
12481248
if f.dtype.char in ["M", "m"]:
1249-
y = f.view('int64')
1249+
y = f.view(otype)
12501250
else:
12511251
y = f
12521252

@@ -1266,19 +1266,19 @@ def gradient(f, *varargs, **kwargs):
12661266
slice2[axis] = slice(2, None)
12671267
slice3[axis] = slice(None, -2)
12681268
# 1D equivalent -- out[1:-1] = (y[2:] - y[:-2])/2.0
1269-
out[slice1] = (y[slice2] - y[slice3])/2.0
1269+
out[tuple(slice1)] = (y[tuple(slice2)] - y[tuple(slice3)])/2.0
12701270

12711271
slice1[axis] = 0
12721272
slice2[axis] = 1
12731273
slice3[axis] = 0
12741274
# 1D equivalent -- out[0] = (y[1] - y[0])
1275-
out[slice1] = (y[slice2] - y[slice3])
1275+
out[tuple(slice1)] = (y[tuple(slice2)] - y[tuple(slice3)])
12761276

12771277
slice1[axis] = -1
12781278
slice2[axis] = -1
12791279
slice3[axis] = -2
12801280
# 1D equivalent -- out[-1] = (y[-1] - y[-2])
1281-
out[slice1] = (y[slice2] - y[slice3])
1281+
out[tuple(slice1)] = (y[tuple(slice2)] - y[tuple(slice3)])
12821282

12831283
# Numerical differentiation: 2st order edges, 2nd order interior
12841284
else:
@@ -1289,21 +1289,23 @@ def gradient(f, *varargs, **kwargs):
12891289
slice2[axis] = slice(2, None)
12901290
slice3[axis] = slice(None, -2)
12911291
# 1D equivalent -- out[1:-1] = (y[2:] - y[:-2])/2.0
1292-
out[slice1] = (y[slice2] - y[slice3])/2.0
1292+
out[tuple(slice1)] = (y[tuple(slice2)] - y[tuple(slice3)])/2.0
12931293

12941294
slice1[axis] = 0
12951295
slice2[axis] = 0
12961296
slice3[axis] = 1
12971297
slice4[axis] = 2
12981298
# 1D equivalent -- out[0] = -(3*y[0] - 4*y[1] + y[2]) / 2.0
1299-
out[slice1] = -(3.0*y[slice2] - 4.0*y[slice3] + y[slice4])/2.0
1299+
out[tuple(slice1)] = -(3.0*y[tuple(slice2)] - 4.0*y[tuple(slice3)] +
1300+
y[tuple(slice4)]) / 2.0
13001301

13011302
slice1[axis] = -1
13021303
slice2[axis] = -1
13031304
slice3[axis] = -2
13041305
slice4[axis] = -3
13051306
# 1D equivalent -- out[-1] = (3*y[-1] - 4*y[-2] + y[-3])
1306-
out[slice1] = (3.0*y[slice2] - 4.0*y[slice3] + y[slice4])/2.0
1307+
out[tuple(slice1)] = (3.0*y[tuple(slice2)] - 4.0*y[tuple(slice3)] +
1308+
y[tuple(slice4)]) / 2.0
13071309

13081310
# divide by step size
13091311
out /= dx[i]
@@ -1601,6 +1603,7 @@ def unwrap(p, discont=pi, axis=-1):
16011603
dd = diff(p, axis=axis)
16021604
slice1 = [slice(None, None)]*nd # full slices
16031605
slice1[axis] = slice(1, None)
1606+
slice1 = tuple(slice1)
16041607
ddmod = mod(dd + pi, 2*pi) - pi
16051608
_nx.copyto(ddmod, pi, where=(ddmod == -pi) & (dd > 0))
16061609
ph_correct = ddmod - dd
@@ -3344,6 +3347,7 @@ def _median(a, axis=None, out=None, overwrite_input=False):
33443347
indexer[axis] = slice(index, index+1)
33453348
else:
33463349
indexer[axis] = slice(index-1, index+1)
3350+
indexer = tuple(indexer)
33473351

33483352
# Check if the array contains any nan's
33493353
if np.issubdtype(a.dtype, np.inexact) and sz > 0:
@@ -3713,12 +3717,12 @@ def trapz(y, x=None, dx=1.0, axis=-1):
37133717
slice1[axis] = slice(1, None)
37143718
slice2[axis] = slice(None, -1)
37153719
try:
3716-
ret = (d * (y[slice1] + y[slice2]) / 2.0).sum(axis)
3720+
ret = (d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0).sum(axis)
37173721
except ValueError:
37183722
# Operations didn't work, cast to ndarray
37193723
d = np.asarray(d)
37203724
y = np.asarray(y)
3721-
ret = add.reduce(d * (y[slice1]+y[slice2])/2.0, axis)
3725+
ret = add.reduce(d * (y[tuple(slice1)]+y[tuple(slice2)])/2.0, axis)
37223726
return ret
37233727

37243728

@@ -4009,15 +4013,15 @@ def delete(arr, obj, axis=None):
40094013
pass
40104014
else:
40114015
slobj[axis] = slice(None, start)
4012-
new[slobj] = arr[slobj]
4016+
new[tuple(slobj)] = arr[tuple(slobj)]
40134017
# copy end chunck
40144018
if stop == N:
40154019
pass
40164020
else:
40174021
slobj[axis] = slice(stop-numtodel, None)
40184022
slobj2 = [slice(None)]*ndim
40194023
slobj2[axis] = slice(stop, None)
4020-
new[slobj] = arr[slobj2]
4024+
new[tuple(slobj)] = arr[tuple(slobj2)]
40214025
# copy middle pieces
40224026
if step == 1:
40234027
pass
@@ -4027,9 +4031,9 @@ def delete(arr, obj, axis=None):
40274031
slobj[axis] = slice(start, stop-numtodel)
40284032
slobj2 = [slice(None)]*ndim
40294033
slobj2[axis] = slice(start, stop)
4030-
arr = arr[slobj2]
4034+
arr = arr[tuple(slobj2)]
40314035
slobj2[axis] = keep
4032-
new[slobj] = arr[slobj2]
4036+
new[tuple(slobj)] = arr[tuple(slobj2)]
40334037
if wrap:
40344038
return wrap(new)
40354039
else:
@@ -4056,11 +4060,11 @@ def delete(arr, obj, axis=None):
40564060
newshape[axis] -= 1
40574061
new = empty(newshape, arr.dtype, arr.flags.fnc)
40584062
slobj[axis] = slice(None, obj)
4059-
new[slobj] = arr[slobj]
4063+
new[tuple(slobj)] = arr[tuple(slobj)]
40604064
slobj[axis] = slice(obj, None)
40614065
slobj2 = [slice(None)]*ndim
40624066
slobj2[axis] = slice(obj+1, None)
4063-
new[slobj] = arr[slobj2]
4067+
new[tuple(slobj)] = arr[tuple(slobj2)]
40644068
else:
40654069
if obj.size == 0 and not isinstance(_obj, np.ndarray):
40664070
obj = obj.astype(intp)
@@ -4092,7 +4096,7 @@ def delete(arr, obj, axis=None):
40924096

40934097
keep[obj, ] = False
40944098
slobj[axis] = keep
4095-
new = arr[slobj]
4099+
new = arr[tuple(slobj)]
40964100

40974101
if wrap:
40984102
return wrap(new)
@@ -4267,13 +4271,13 @@ def insert(arr, obj, values, axis=None):
42674271
newshape[axis] += numnew
42684272
new = empty(newshape, arr.dtype, arr.flags.fnc)
42694273
slobj[axis] = slice(None, index)
4270-
new[slobj] = arr[slobj]
4274+
new[tuple(slobj)] = arr[tuple(slobj)]
42714275
slobj[axis] = slice(index, index+numnew)
4272-
new[slobj] = values
4276+
new[tuple(slobj)] = values
42734277
slobj[axis] = slice(index+numnew, None)
42744278
slobj2 = [slice(None)] * ndim
42754279
slobj2[axis] = slice(index, None)
4276-
new[slobj] = arr[slobj2]
4280+
new[tuple(slobj)] = arr[tuple(slobj2)]
42774281
if wrap:
42784282
return wrap(new)
42794283
return new
@@ -4302,8 +4306,8 @@ def insert(arr, obj, values, axis=None):
43024306
slobj2 = [slice(None)]*ndim
43034307
slobj[axis] = indices
43044308
slobj2[axis] = old_mask
4305-
new[slobj] = values
4306-
new[slobj2] = arr
4309+
new[tuple(slobj)] = values
4310+
new[tuple(slobj2)] = arr
43074311

43084312
if wrap:
43094313
return wrap(new)

numpy/ma/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5347,8 +5347,11 @@ def sort(self, axis=-1, kind='quicksort', order=None,
53475347
idx = np.meshgrid(*[np.arange(x) for x in self.shape], sparse=True,
53485348
indexing='ij')
53495349
idx[axis] = sidx
5350+
idx = tuple(idx)
5351+
53505352
tmp_mask = self._mask[idx].flat
53515353
tmp_data = self._data[idx].flat
5354+
53525355
self._data.flat = tmp_data
53535356
self._mask.flat = tmp_mask
53545357
return
@@ -6424,7 +6427,9 @@ def sort(a, axis=-1, kind='quicksort', order=None, endwith=True, fill_value=None
64246427
indx = np.meshgrid(*[np.arange(x) for x in a.shape], sparse=True,
64256428
indexing='ij')
64266429
indx[axis] = sindx
6430+
indx = tuple(indx)
64276431
return a[indx]
6432+
64286433
sort.__doc__ = MaskedArray.sort.__doc__
64296434

64306435

0 commit comments

Comments
 (0)
0