summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnirudh Subramanian <anirudh2290@ufl.edu>2020-06-08 07:10:10 -0700
committerGitHub <noreply@github.com>2020-06-08 09:10:10 -0500
commit1d799fdcadeaa155855d4d91e39e1c83b9649dd8 (patch)
treeeb98c8c269ffce3d68707ad4511177114324734a
parentee60cc9164e5a20b98ee6722e25b0df41e411675 (diff)
downloadnumpy-1d799fdcadeaa155855d4d91e39e1c83b9649dd8.tar.gz
DEP: Deprecate inexact matches for mode, searchside (gh-16056)
Previously misspelling were allowed by these functions as long as e.g. the first letter matched. They will now check whether the match is exact giving a deprecation warning when it is not. Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
-rw-r--r--doc/release/upcoming_changes/16056.deprecation.rst13
-rw-r--r--numpy/core/_add_newdocs.py30
-rw-r--r--numpy/core/_dtype.py6
-rw-r--r--numpy/core/src/multiarray/conversion_utils.c48
-rw-r--r--numpy/core/tests/test_conversion_utils.py30
-rw-r--r--numpy/core/tests/test_deprecations.py10
-rw-r--r--numpy/core/tests/test_multiarray.py72
7 files changed, 138 insertions, 71 deletions
diff --git a/doc/release/upcoming_changes/16056.deprecation.rst b/doc/release/upcoming_changes/16056.deprecation.rst
new file mode 100644
index 000000000..788b8c30a
--- /dev/null
+++ b/doc/release/upcoming_changes/16056.deprecation.rst
@@ -0,0 +1,13 @@
+Inexact matches for mode and searchside are deprecated
+------------------------------------------------------
+Inexact and case insensitive matches for mode and searchside were
+valid inputs earlier and will give a DeprecationWarning now.
+For example, below are some example usages which are now deprecated and will
+give a DeprecationWarning.
+
+ import numpy as np
+ arr = np.array([[3, 6, 6], [4, 5, 1]])
+ # mode: inexact match
+ np.ravel_multi_index(arr, (7, 6), mode="clap") # should be "clip"
+ # searchside: inexact match
+ np.searchsorted(arr[0], 4, side='random') # should be "right"
diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py
index 20ad39b05..688238af3 100644
--- a/numpy/core/_add_newdocs.py
+++ b/numpy/core/_add_newdocs.py
@@ -3243,15 +3243,13 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('newbyteorder',
below. `new_order` codes can be any of:
* 'S' - swap dtype from current to opposite endian
- * {'<', 'L'} - little endian
- * {'>', 'B'} - big endian
- * {'=', 'N'} - native order
+ * {'<', 'little'} - little endian
+ * {'>', 'big'} - big endian
+ * '=' - native order, equivalent to `sys.byteorder`
* {'|', 'I'} - ignore (no change to byte order)
The default value ('S') results in swapping the current
- byte order. The code does a case-insensitive check on the first
- letter of `new_order` for the alternatives above. For example,
- any of 'B' or 'b' or 'biggish' are valid to specify big-endian.
+ byte order.
Returns
@@ -5666,15 +5664,11 @@ add_newdoc('numpy.core.multiarray', 'dtype', ('newbyteorder',
byte order. `new_order` codes can be any of:
* 'S' - swap dtype from current to opposite endian
- * {'<', 'L'} - little endian
- * {'>', 'B'} - big endian
- * {'=', 'N'} - native order
+ * {'<', 'little'} - little endian
+ * {'>', 'big'} - big endian
+ * '=' - native order
* {'|', 'I'} - ignore (no change to byte order)
- The code does a case-insensitive check on the first letter of
- `new_order` for these alternatives. For example, any of '>'
- or 'B' or 'b' or 'brian' are valid to specify big-endian.
-
Returns
-------
new_dtype : dtype
@@ -6041,9 +6035,9 @@ add_newdoc('numpy.core.numerictypes', 'generic', ('newbyteorder',
The `new_order` code can be any from the following:
* 'S' - swap dtype from current to opposite endian
- * {'<', 'L'} - little endian
- * {'>', 'B'} - big endian
- * {'=', 'N'} - native order
+ * {'<', 'little'} - little endian
+ * {'>', 'big'} - big endian
+ * '=' - native order
* {'|', 'I'} - ignore (no change to byte order)
Parameters
@@ -6051,9 +6045,7 @@ add_newdoc('numpy.core.numerictypes', 'generic', ('newbyteorder',
new_order : str, optional
Byte order to force; a value from the byte order specifications
above. The default value ('S') results in swapping the current
- byte order. The code does a case-insensitive check on the first
- letter of `new_order` for the alternatives above. For example,
- any of 'B' or 'b' or 'biggish' are valid to specify big-endian.
+ byte order.
Returns
diff --git a/numpy/core/_dtype.py b/numpy/core/_dtype.py
index 76d0b8149..50aeeb5bc 100644
--- a/numpy/core/_dtype.py
+++ b/numpy/core/_dtype.py
@@ -160,13 +160,13 @@ def _scalar_str(dtype, short):
def _byte_order_str(dtype):
""" Normalize byteorder to '<' or '>' """
# hack to obtain the native and swapped byte order characters
- swapped = np.dtype(int).newbyteorder('s')
- native = swapped.newbyteorder('s')
+ swapped = np.dtype(int).newbyteorder('S')
+ native = swapped.newbyteorder('S')
byteorder = dtype.byteorder
if byteorder == '=':
return native.byteorder
- if byteorder == 's':
+ if byteorder == 'S':
# TODO: this path can never be reached
return swapped.byteorder
elif byteorder == '|':
diff --git a/numpy/core/src/multiarray/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c
index 0e49b0d63..e41fdc8f1 100644
--- a/numpy/core/src/multiarray/conversion_utils.c
+++ b/numpy/core/src/multiarray/conversion_utils.c
@@ -371,8 +371,11 @@ string_converter_helper(
int ret = str_func(str, length, out);
Py_DECREF(str_object);
if (ret < 0) {
+ /* str_func returns -1 without an exception if the value is wrong */
+ if (!PyErr_Occurred()) {
PyErr_Format(PyExc_ValueError,
"%s %s (got %R)", name, message, object);
+ }
return NPY_FAIL;
}
return NPY_SUCCEED;
@@ -385,8 +388,8 @@ static int byteorder_parser(char const *str, Py_ssize_t length, void *data)
if (length < 1) {
return -1;
}
- else if (str[0] == NPY_BIG || str[0] == NPY_LITTLE
- || str[0] == NPY_NATIVE || str[0] == NPY_IGNORE) {
+ else if (str[0] == NPY_BIG || str[0] == NPY_LITTLE ||
+ str[0] == NPY_NATIVE || str[0] == NPY_IGNORE) {
*endian = str[0];
return 0;
}
@@ -508,21 +511,36 @@ PyArray_SelectkindConverter(PyObject *obj, NPY_SELECTKIND *selectkind)
static int searchside_parser(char const *str, Py_ssize_t length, void *data)
{
NPY_SEARCHSIDE *side = (NPY_SEARCHSIDE *)data;
+ int is_exact = 0;
if (length < 1) {
return -1;
}
else if (str[0] == 'l' || str[0] == 'L') {
*side = NPY_SEARCHLEFT;
- return 0;
+ is_exact = (length == 4 && strcmp(str, "left") == 0);
}
else if (str[0] == 'r' || str[0] == 'R') {
*side = NPY_SEARCHRIGHT;
- return 0;
+ is_exact = (length == 5 && strcmp(str, "right") == 0);
}
else {
return -1;
}
+
+ /* Filters out the case sensitive/non-exact
+ * match inputs and other inputs and outputs DeprecationWarning
+ */
+ if (!is_exact) {
+ /* NumPy 1.20, 2020-05-19 */
+ if (DEPRECATE("inexact matches and case insensitive matches "
+ "for search side are deprecated, please use "
+ "one of 'left' or 'right' instead.") < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
}
/*NUMPY_API
@@ -581,24 +599,40 @@ PyArray_OrderConverter(PyObject *object, NPY_ORDER *val)
static int clipmode_parser(char const *str, Py_ssize_t length, void *data)
{
NPY_CLIPMODE *val = (NPY_CLIPMODE *)data;
+ int is_exact = 0;
+
if (length < 1) {
return -1;
}
if (str[0] == 'C' || str[0] == 'c') {
*val = NPY_CLIP;
- return 0;
+ is_exact = (length == 4 && strcmp(str, "clip") == 0);
}
else if (str[0] == 'W' || str[0] == 'w') {
*val = NPY_WRAP;
- return 0;
+ is_exact = (length == 4 && strcmp(str, "wrap") == 0);
}
else if (str[0] == 'R' || str[0] == 'r') {
*val = NPY_RAISE;
- return 0;
+ is_exact = (length == 5 && strcmp(str, "raise") == 0);
}
else {
return -1;
}
+
+ /* Filters out the case sensitive/non-exact
+ * match inputs and other inputs and outputs DeprecationWarning
+ */
+ if (!is_exact) {
+ /* Numpy 1.20, 2020-05-19 */
+ if (DEPRECATE("inexact matches and case insensitive matches "
+ "for clip mode are deprecated, please use "
+ "one of 'clip', 'raise', or 'wrap' instead.") < 0) {
+ return -1;
+ }
+ }
+
+ return 0;
}
/*NUMPY_API
diff --git a/numpy/core/tests/test_conversion_utils.py b/numpy/core/tests/test_conversion_utils.py
index e96113d09..d8849ee29 100644
--- a/numpy/core/tests/test_conversion_utils.py
+++ b/numpy/core/tests/test_conversion_utils.py
@@ -7,19 +7,31 @@ import pytest
import numpy as np
import numpy.core._multiarray_tests as mt
+from numpy.testing import assert_warns
class StringConverterTestCase:
allow_bytes = True
case_insensitive = True
exact_match = False
+ warn = True
def _check_value_error(self, val):
pattern = r'\(got {}\)'.format(re.escape(repr(val)))
with pytest.raises(ValueError, match=pattern) as exc:
self.conv(val)
+ def _check_conv_assert_warn(self, val, expected):
+ if self.warn:
+ with assert_warns(DeprecationWarning) as exc:
+ assert self.conv(val) == expected
+ else:
+ assert self.conv(val) == expected
+
def _check(self, val, expected):
+ """Takes valid non-deprecated inputs for converters,
+ runs converters on inputs, checks correctness of outputs,
+ warnings and errors"""
assert self.conv(val) == expected
if self.allow_bytes:
@@ -33,13 +45,13 @@ class StringConverterTestCase:
self._check_value_error(val[:1])
self._check_value_error(val + '\0')
else:
- assert self.conv(val[:1]) == expected
+ self._check_conv_assert_warn(val[:1], expected)
if self.case_insensitive:
if val != val.lower():
- assert self.conv(val.lower()) == expected
+ self._check_conv_assert_warn(val.lower(), expected)
if val != val.upper():
- assert self.conv(val.upper()) == expected
+ self._check_conv_assert_warn(val.upper(), expected)
else:
if val != val.lower():
self._check_value_error(val.lower())
@@ -69,6 +81,8 @@ class StringConverterTestCase:
class TestByteorderConverter(StringConverterTestCase):
""" Tests of PyArray_ByteorderConverter """
conv = mt.run_byteorder_converter
+ warn = False
+
def test_valid(self):
for s in ['big', '>']:
self._check(s, 'NPY_BIG')
@@ -85,10 +99,12 @@ class TestByteorderConverter(StringConverterTestCase):
class TestSortkindConverter(StringConverterTestCase):
""" Tests of PyArray_SortkindConverter """
conv = mt.run_sortkind_converter
+ warn = False
+
def test_valid(self):
- self._check('quick', 'NPY_QUICKSORT')
- self._check('heap', 'NPY_HEAPSORT')
- self._check('merge', 'NPY_STABLESORT') # alias
+ self._check('quicksort', 'NPY_QUICKSORT')
+ self._check('heapsort', 'NPY_HEAPSORT')
+ self._check('mergesort', 'NPY_STABLESORT') # alias
self._check('stable', 'NPY_STABLESORT')
@@ -113,6 +129,8 @@ class TestSearchsideConverter(StringConverterTestCase):
class TestOrderConverter(StringConverterTestCase):
""" Tests of PyArray_OrderConverter """
conv = mt.run_order_converter
+ warn = False
+
def test_valid(self):
self._check('c', 'NPY_CORDER')
self._check('f', 'NPY_FORTRANORDER')
diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py
index 523638a35..01924410f 100644
--- a/numpy/core/tests/test_deprecations.py
+++ b/numpy/core/tests/test_deprecations.py
@@ -645,3 +645,13 @@ class TestIncorrectAdvancedIndexWithEmptyResult(_DeprecationTestCase):
self.assert_not_deprecated(arr.__getitem__, args=(index,))
self.assert_not_deprecated(arr.__setitem__,
args=(index, np.empty((2, 0, 2))))
+
+
+class TestNonExactMatchDeprecation(_DeprecationTestCase):
+ # 2020-04-22
+ def test_non_exact_match(self):
+ arr = np.array([[3, 6, 6], [4, 5, 1]])
+ # misspelt mode check
+ self.assert_deprecated(lambda: np.ravel_multi_index(arr, (7, 6), mode='Cilp'))
+ # using completely different word with first character as R
+ self.assert_deprecated(lambda: np.searchsorted(arr[0], 4, side='Random'))
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 1a8268eb8..e116077f9 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -2153,10 +2153,10 @@ class TestMethods:
# check double
a = np.array([0, 1, np.nan])
msg = "Test real searchsorted with nans, side='l'"
- b = a.searchsorted(a, side='l')
+ b = a.searchsorted(a, side='left')
assert_equal(b, np.arange(3), msg)
msg = "Test real searchsorted with nans, side='r'"
- b = a.searchsorted(a, side='r')
+ b = a.searchsorted(a, side='right')
assert_equal(b, np.arange(1, 4), msg)
# check keyword arguments
a.searchsorted(v=1)
@@ -2165,10 +2165,10 @@ class TestMethods:
a.real += [0, 0, 1, 1, 0, 1, np.nan, np.nan, np.nan]
a.imag += [0, 1, 0, 1, np.nan, np.nan, 0, 1, np.nan]
msg = "Test complex searchsorted with nans, side='l'"
- b = a.searchsorted(a, side='l')
+ b = a.searchsorted(a, side='left')
assert_equal(b, np.arange(9), msg)
msg = "Test complex searchsorted with nans, side='r'"
- b = a.searchsorted(a, side='r')
+ b = a.searchsorted(a, side='right')
assert_equal(b, np.arange(1, 10), msg)
msg = "Test searchsorted with little endian, side='l'"
a = np.array([0, 128], dtype='<i4')
@@ -2181,21 +2181,21 @@ class TestMethods:
# Check 0 elements
a = np.ones(0)
- b = a.searchsorted([0, 1, 2], 'l')
+ b = a.searchsorted([0, 1, 2], 'left')
assert_equal(b, [0, 0, 0])
- b = a.searchsorted([0, 1, 2], 'r')
+ b = a.searchsorted([0, 1, 2], 'right')
assert_equal(b, [0, 0, 0])
a = np.ones(1)
# Check 1 element
- b = a.searchsorted([0, 1, 2], 'l')
+ b = a.searchsorted([0, 1, 2], 'left')
assert_equal(b, [0, 0, 1])
- b = a.searchsorted([0, 1, 2], 'r')
+ b = a.searchsorted([0, 1, 2], 'right')
assert_equal(b, [0, 1, 1])
# Check all elements equal
a = np.ones(2)
- b = a.searchsorted([0, 1, 2], 'l')
+ b = a.searchsorted([0, 1, 2], 'left')
assert_equal(b, [0, 0, 2])
- b = a.searchsorted([0, 1, 2], 'r')
+ b = a.searchsorted([0, 1, 2], 'right')
assert_equal(b, [0, 2, 2])
# Test searching unaligned array
@@ -2204,21 +2204,21 @@ class TestMethods:
unaligned = aligned[1:].view(a.dtype)
unaligned[:] = a
# Test searching unaligned array
- b = unaligned.searchsorted(a, 'l')
+ b = unaligned.searchsorted(a, 'left')
assert_equal(b, a)
- b = unaligned.searchsorted(a, 'r')
+ b = unaligned.searchsorted(a, 'right')
assert_equal(b, a + 1)
# Test searching for unaligned keys
- b = a.searchsorted(unaligned, 'l')
+ b = a.searchsorted(unaligned, 'left')
assert_equal(b, a)
- b = a.searchsorted(unaligned, 'r')
+ b = a.searchsorted(unaligned, 'right')
assert_equal(b, a + 1)
# Test smart resetting of binsearch indices
a = np.arange(5)
- b = a.searchsorted([6, 5, 4], 'l')
+ b = a.searchsorted([6, 5, 4], 'left')
assert_equal(b, [5, 5, 4])
- b = a.searchsorted([6, 5, 4], 'r')
+ b = a.searchsorted([6, 5, 4], 'right')
assert_equal(b, [5, 5, 5])
# Test all type specific binary search functions
@@ -2233,16 +2233,16 @@ class TestMethods:
else:
a = np.arange(0, 5, dtype=dt)
out = np.arange(5)
- b = a.searchsorted(a, 'l')
+ b = a.searchsorted(a, 'left')
assert_equal(b, out)
- b = a.searchsorted(a, 'r')
+ b = a.searchsorted(a, 'right')
assert_equal(b, out + 1)
# Test empty array, use a fresh array to get warnings in
# valgrind if access happens.
e = np.ndarray(shape=0, buffer=b'', dtype=dt)
- b = e.searchsorted(a, 'l')
+ b = e.searchsorted(a, 'left')
assert_array_equal(b, np.zeros(len(a), dtype=np.intp))
- b = a.searchsorted(e, 'l')
+ b = a.searchsorted(e, 'left')
assert_array_equal(b, np.zeros(0, dtype=np.intp))
def test_searchsorted_unicode(self):
@@ -2297,9 +2297,9 @@ class TestMethods:
s = a.argsort()
k = [0, 1, 2, 3, 5]
expected = [0, 20, 40, 60, 80]
- assert_equal(a.searchsorted(k, side='l', sorter=s), expected)
+ assert_equal(a.searchsorted(k, side='left', sorter=s), expected)
expected = [20, 40, 60, 80, 100]
- assert_equal(a.searchsorted(k, side='r', sorter=s), expected)
+ assert_equal(a.searchsorted(k, side='right', sorter=s), expected)
# Test searching unaligned array
keys = np.arange(10)
@@ -2310,15 +2310,15 @@ class TestMethods:
unaligned = aligned[1:].view(a.dtype)
# Test searching unaligned array
unaligned[:] = a
- b = unaligned.searchsorted(keys, 'l', s)
+ b = unaligned.searchsorted(keys, 'left', s)
assert_equal(b, keys)
- b = unaligned.searchsorted(keys, 'r', s)
+ b = unaligned.searchsorted(keys, 'right', s)
assert_equal(b, keys + 1)
# Test searching for unaligned keys
unaligned[:] = keys
- b = a.searchsorted(unaligned, 'l', s)
+ b = a.searchsorted(unaligned, 'left', s)
assert_equal(b, keys)
- b = a.searchsorted(unaligned, 'r', s)
+ b = a.searchsorted(unaligned, 'right', s)
assert_equal(b, keys + 1)
# Test all type specific indirect binary search functions
@@ -2339,16 +2339,16 @@ class TestMethods:
# from np.intp in all platforms, to check for #4698
s = np.array([4, 2, 3, 0, 1], dtype=np.int16)
out = np.array([3, 4, 1, 2, 0], dtype=np.intp)
- b = a.searchsorted(a, 'l', s)
+ b = a.searchsorted(a, 'left', s)
assert_equal(b, out)
- b = a.searchsorted(a, 'r', s)
+ b = a.searchsorted(a, 'right', s)
assert_equal(b, out + 1)
# Test empty array, use a fresh array to get warnings in
# valgrind if access happens.
e = np.ndarray(shape=0, buffer=b'', dtype=dt)
- b = e.searchsorted(a, 'l', s[:0])
+ b = e.searchsorted(a, 'left', s[:0])
assert_array_equal(b, np.zeros(len(a), dtype=np.intp))
- b = a.searchsorted(e, 'l', s)
+ b = a.searchsorted(e, 'left', s)
assert_array_equal(b, np.zeros(0, dtype=np.intp))
# Test non-contiguous sorter array
@@ -2358,9 +2358,9 @@ class TestMethods:
srt[::2] = [4, 2, 3, 0, 1]
s = srt[::2]
out = np.array([3, 4, 1, 2, 0], dtype=np.intp)
- b = a.searchsorted(a, 'l', s)
+ b = a.searchsorted(a, 'left', s)
assert_equal(b, out)
- b = a.searchsorted(a, 'r', s)
+ b = a.searchsorted(a, 'right', s)
assert_equal(b, out + 1)
def test_searchsorted_return_type(self):
@@ -2370,10 +2370,10 @@ class TestMethods:
a = np.arange(5).view(A)
b = np.arange(1, 3).view(A)
s = np.arange(5).view(A)
- assert_(not isinstance(a.searchsorted(b, 'l'), A))
- assert_(not isinstance(a.searchsorted(b, 'r'), A))
- assert_(not isinstance(a.searchsorted(b, 'l', s), A))
- assert_(not isinstance(a.searchsorted(b, 'r', s), A))
+ assert_(not isinstance(a.searchsorted(b, 'left'), A))
+ assert_(not isinstance(a.searchsorted(b, 'right'), A))
+ assert_(not isinstance(a.searchsorted(b, 'left', s), A))
+ assert_(not isinstance(a.searchsorted(b, 'right', s), A))
def test_argpartition_out_of_range(self):
# Test out of range values in kth raise an error, gh-5469