8000 DEP: Deprecate that comparisons ignore errors. · numpy/numpy@9b8f6c7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9b8f6c7

Browse files
committed
DEP: Deprecate that comparisons ignore errors.
This means that for example broadcasting errors get raised. The array_equiv function is changed to explicitely test if broadcasting is possible. It may be nice to do this test differently, but I am not sure if that is possible. Create a FutureWarning for comparisons to None, which should result in areal elementwise (object) comparisons. Slightly adepted a wrong test. Poly changes: Some changes in the polycode was necessary, the one is probably a bug fix, the other needs to be thought over, since len check is not perfect maybe, since it is more liekly to raise raise an error. Closes gh-3759 and gh-1608
1 parent 84831ca commit 9b8f6c7

File tree

6 files changed

+105
-10
lines changed

6 files changed

+105
-10
lines changed

numpy/core/numeric.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,10 +2388,12 @@ def array_equiv(a1, a2):
23882388
except:
23892389
return False
23902390
try:
2391-
return bool(asarray(a1 == a2).all())
2392-
except ValueError:
2391+
multiarray.broadcast(a1, a2)
2392+
except:
23932393
return False
23942394

2395+
return bool(asarray(a1 == a2).all())
2396+
23952397

23962398
_errdict = {"ignore":ERR_IGNORE,
23972399
"warn":ERR_WARN,

numpy/core/src/multiarray/arrayobject.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,10 @@ array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op)
12921292
break;
12931293
case Py_EQ:
12941294
if (other == Py_None) {
1295+
if (DEPRECATE_FUTUREWARNING("comparison to `None` will result in "
1296+
"an elementwise object comparison in the future.") < 0) {
1297+
return NULL;
1298+
}
12951299
Py_INCREF(Py_False);
12961300
return Py_False;
12971301
}
@@ -1347,13 +1351,26 @@ array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op)
13471351
* indicate that
13481352
*/
13491353
if (result == NULL) {
1354+
/*
1355+
* Comparisons should raise errors when element-wise comparison
1356+
* is not possible.
1357+
*/
13501358
PyErr_Clear();
1359+
if (DEPRECATE("elementwise comparison failed; "
1360+
"this will raise the error in the future.") < 0) {
1361+
return NULL;
1362+
}
1363+
13511364
Py_INCREF(Py_NotImplemented);
13521365
return Py_NotImplemented;
13531366
}
13541367
break;
13551368
case Py_NE:
13561369
if (other == Py_None) {
1370+
if (DEPRECATE_FUTUREWARNING("comparison to `None` will result in "
1371+
"an elementwise object comparison in the future.") < 0) {
1372+
return NULL;
1373+
}
13571374
Py_INCREF(Py_True);
13581375
return Py_True;
13591376
}
@@ -1404,7 +1421,16 @@ array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op)
14041421
}
14051422

14061423
if (result == NULL) {
1424+
/*
1425+
* Comparisons should raise errors when element-wise comparison
1426+
* is not possible.
1427+
*/
14071428
PyErr_Clear();
1429+
if (DEPRECATE("elementwise comparison failed; "
1430+
"this will raise the error in the future.") < 0) {
1431+
return NULL;
1432+
}
1433+
14081434
Py_INCREF(Py_NotImplemented);
14091435
return Py_NotImplemented;
14101436
}

numpy/core/tests/test_deprecations.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def assert_deprecated(self, function, num=1, ignore_others=False,
8686
elif not ignore_others:
8787
raise AssertionError("expected DeprecationWarning but %s given"
8888
% warning.category)
89-
if num_found != num:
89+
if num is not None and num_found != num:
9090
raise AssertionError("%i warnings found but %i expected"
9191
% (len(self.log), num))
9292

@@ -375,5 +375,57 @@ def test(self):
375375
assert_warns(np.VisibleDeprecationWarning, np.rank, a)
376376

377377

378+
class TestComparisonDepreactions(_DeprecationTestCase):
379+
"""This tests the deprecation, for non-elementwise comparison logic.
380+
This used to mean that when an error occured during element-wise comparison
381+
(i.e. broadcasting) NotImplemented was returned, but also in the comparison
382+
itself, False was given instead of the error.
383+
384+
Also test FutureWarning for the None comparison.
385+
"""
386+
387+
message = "elementwise comparison failed; " \
388+
"this will raise the error in the future."
389+
390+
def test_normal_types(self):
391+
for op in (operator.eq, operator.ne):
392+
# Broadcasting errors:
393+
self.assert_deprecated(op, args=(np.zeros(3), []))
394+
a = np.zeros(3, dtype='i,i')
395+
# (warning is issued a couple of times here)
396+
self.assert_deprecated(op, args=(a, a[:-1]), num=None)
397+
398+
# Element comparison error (numpy array can't be compared).
399+
a = np.array([1, np.array([1,2,3])], dtype=object)
400+
self.assert_deprecated(op, args=(a, a), num=None)
401+
402+
403+
def test_string(self):
404+
# For two string arrays, strings always raised the broadcasting error:
405+
a = np.array(['a', 'b'])
406+
b = np.array(['a', 'b', 'c'])
407+
assert_raises(ValueError, lambda x, y: x == y, a, b)
408+
409+
# The empty list is not cast to string, this is only to document
410+
# that fact (it likely should be changed). This means that the
411+
# following works (and returns False) due to dtype mismatch:
412+
a == []
413+
414+
415+
def test_none_comparison(self):
416+
# Test comparison of None, which should result in elementwise
417+
# comparison in the future. [1, 2] == None should be [False, False].
418+
with warnings.catch_warnings():
419+
warnings.filterwarnings('always', '', FutureWarning)
420+
a = np.array([1, 2])
421+
assert_warns(FutureWarning, operator.eq, np.arange(3), None)
422+
assert_warns(FutureWarning, operator.ne, np.arange(3), None)
423+
424+
with warnings.catch_warnings():
425+
warnings.filterwarnings('error', '', FutureWarning)
426+
assert_raises(FutureWarning, operator.eq, np.arange(3), None)
427+
assert_raises(FutureWarning, operator.ne, np.arange(3), None)
428+
429+
378430
if __name__ == "__main__":
379431
run_module_suite()

numpy/core/tests/test_multiarray.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,9 @@ def test_subarray_field_access(self):
561561
a = np.zeros((3, 5), dtype=[('a', ('i4', (2, 2)))])
562562
a['a'] = np.arange(60).reshape(3, 5, 2, 2)
563563

564-
# Since the subarray is always in C-order, these aren't equal
565-
assert_(np.any(a['a'].T != a.T['a']))
564+
# Since the subarray is always in C-order, a transpose
565+
# does not swap the subarray:
566+
assert_array_equal(a.T['a'], a['a'].transpose(1, 0, 2, 3))
566567

567568
# In Fortran order, the subarray gets appended
568569
# like in all other cases, not prepended as a special case

numpy/lib/polynomial.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,10 +1193,24 @@ def __rdiv__(self, other):
11931193
__rtruediv__ = __rdiv__
11941194

11951195
def __eq__(self, other):
1196-
return NX.alltrue(self.coeffs == other.coeffs)
1196+
dim = min(self.coeffs.shape[0], other.coeffs.shape[0])
1197+
if (self.coeffs[-dim:] != other.coeffs[-dim:]).any():
1198+
return False
1199+
elif (self.coeffs[:-dim] != 0).any():
1200+
return False
1201+
elif (other.coeffs[:-dim] != 0).any():
1202+
return False
1203+
return True
11971204

11981205
def __ne__(self, other):
1199-
return NX.any(self.coeffs != other.coeffs)
1206+
dim = min(self.coeffs.shape[0], other.coeffs.shape[0])
1207+
if (self.coeffs[-dim:] != other.coeffs[-dim:]).any():
1208+
return True
1209+
elif (self.coeffs[:-dim] != 0).any():
1210+
return True
1211+
elif (other.coeffs[:-dim] != 0).any():
1212+
return True
1213+
return False
12001214

12011215
def __setattr__(self, key, val):
12021216
raise ValueError("Attributes cannot be changed this way.")

numpy/polynomial/polytemplate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ def fit(x, y, deg, domain=None, rcond=None, full=False, w=None,
737737
then a minimal domain that covers the points `x` is chosen. If
738738
``[]`` the default domain ``$domain`` is used. The default
739739
value is $domain in numpy 1.4.x and ``None`` in later versions.
740-
The ``'[]`` value was added in numpy 1.5.0.
740+
The ``[]`` value was added in numpy 1.5.0.
741741
rcond : float, optional
742742
Relative condition number of the fit. Singular values smaller
743743
than this relative to the largest singular value will be
@@ -780,10 +780,10 @@ def fit(x, y, deg, domain=None, rcond=None, full=False, w=None,
780780
"""
781781
if domain is None:
782782
domain = pu.getdomain(x)
783-
elif domain == []:
783+
elif len(domain) == 0:
784784
domain = $domain
785785
786-
if window == []:
786+
if len(window) == 0:
787787
window = $domain
788788
789789
xnew = pu.mapdomain(x, domain, window)

0 commit comments

Comments
 (0)
0