diff options
| author | Anirudh Subramanian <anirudh2290@ufl.edu> | 2020-06-08 07:10:10 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-08 09:10:10 -0500 |
| commit | 1d799fdcadeaa155855d4d91e39e1c83b9649dd8 (patch) | |
| tree | eb98c8c269ffce3d68707ad4511177114324734a | |
| parent | ee60cc9164e5a20b98ee6722e25b0df41e411675 (diff) | |
| download | numpy-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.rst | 13 | ||||
| -rw-r--r-- | numpy/core/_add_newdocs.py | 30 | ||||
| -rw-r--r-- | numpy/core/_dtype.py | 6 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/conversion_utils.c | 48 | ||||
| -rw-r--r-- | numpy/core/tests/test_conversion_utils.py | 30 | ||||
| -rw-r--r-- | numpy/core/tests/test_deprecations.py | 10 | ||||
| -rw-r--r-- | numpy/core/tests/test_multiarray.py | 72 |
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 |
