diff options
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/src/multiarray/textreading/parser_config.h | 5 | ||||
-rw-r--r-- | numpy/core/src/multiarray/textreading/readtext.c | 1 | ||||
-rw-r--r-- | numpy/core/src/multiarray/textreading/str_to_int.c | 51 | ||||
-rw-r--r-- | numpy/core/tests/test_deprecations.py | 19 |
4 files changed, 72 insertions, 4 deletions
diff --git a/numpy/core/src/multiarray/textreading/parser_config.h b/numpy/core/src/multiarray/textreading/parser_config.h index 67b5c8483..022ba952c 100644 --- a/numpy/core/src/multiarray/textreading/parser_config.h +++ b/numpy/core/src/multiarray/textreading/parser_config.h @@ -59,6 +59,11 @@ typedef struct { */ bool python_byte_converters; bool c_byte_converters; + /* + * Flag to store whether a warning was already given for an integer being + * parsed by first converting to a float. + */ + bool gave_int_via_float_warning; } parser_config; diff --git a/numpy/core/src/multiarray/textreading/readtext.c b/numpy/core/src/multiarray/textreading/readtext.c index 7af5ee891..9804fd462 100644 --- a/numpy/core/src/multiarray/textreading/readtext.c +++ b/numpy/core/src/multiarray/textreading/readtext.c @@ -201,6 +201,7 @@ _load_from_filelike(PyObject *NPY_UNUSED(mod), .ignore_leading_whitespace = false, .python_byte_converters = false, .c_byte_converters = false, + .gave_int_via_float_warning = false, }; bool filelike = true; diff --git a/numpy/core/src/multiarray/textreading/str_to_int.c b/numpy/core/src/multiarray/textreading/str_to_int.c index 11b03e31c..0dd6c0b0e 100644 --- a/numpy/core/src/multiarray/textreading/str_to_int.c +++ b/numpy/core/src/multiarray/textreading/str_to_int.c @@ -8,8 +8,19 @@ #include <string.h> #include "textreading/str_to_int.h" #include "textreading/parser_config.h" +#include "conversions.h" /* For the deprecated parse-via-float path */ +const char *deprecation_msg = ( + "loadtxt(): Parsing an integer via a float is deprecated. To avoid " + "this warning, you can:\n" + " * make sure the original data is stored as integers.\n" + " * use the `converters=` keyword argument. If you only use\n" + " NumPy 1.23 or later, `converters=float` will normally work.\n" + " * Use `np.loadtxt(...).astype(np.int64)` parsing the file as\n" + " floating point and then convert it. (On all NumPy versions.)\n" + " (Deprecated NumPy 1.23)"); + #define DECLARE_TO_INT(intw, INT_MIN, INT_MAX, byteswap_unaligned) \ NPY_NO_EXPORT int \ to_##intw(PyArray_Descr *descr, \ @@ -19,8 +30,24 @@ int64_t parsed; \ intw##_t x; \ \ - if (str_to_int64(str, end, INT_MIN, INT_MAX, &parsed) < 0) { \ - return -1; \ + if (NPY_UNLIKELY( \ + str_to_int64(str, end, INT_MIN, INT_MAX, &parsed) < 0)) { \ + /* DEPRECATED 2022-07-03, NumPy 1.23 */ \ + double fval; \ + PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE); \ + Py_DECREF(d_descr); /* borrowed */ \ + if (to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) { \ + return -1; \ + } \ + if (!pconfig->gave_int_via_float_warning) { \ + pconfig->gave_int_via_float_warning = true; \ + if (PyErr_WarnEx(PyExc_DeprecationWarning, \ + deprecation_msg, 3) < 0) { \ + return -1; \ + } \ + } \ + pconfig->gave_int_via_float_warning = true; \ + x = (intw##_t)fval; \ } \ else { \ x = (intw##_t)parsed; \ @@ -41,8 +68,24 @@ uint64_t parsed; \ uintw##_t x; \ \ - if (str_to_uint64(str, end, UINT_MAX, &parsed) < 0) { \ - return -1; \ + if (NPY_UNLIKELY( \ + str_to_uint64(str, end, UINT_MAX, &parsed) < 0)) { \ + /* DEPRECATED 2022-07-03, NumPy 1.23 */ \ + double fval; \ + PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE); \ + Py_DECREF(d_descr); /* borrowed */ \ + if (to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) { \ + return -1; \ + } \ + if (!pconfig->gave_int_via_float_warning) { \ + pconfig->gave_int_via_float_warning = true; \ + if (PyErr_WarnEx(PyExc_DeprecationWarning, \ + deprecation_msg, 3) < 0) { \ + return -1; \ + } \ + } \ + pconfig->gave_int_via_float_warning = true; \ + x = (uintw##_t)fval; \ } \ else { \ x = (uintw##_t)parsed; \ diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 09bd5f553..a10dacd49 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -1213,3 +1213,22 @@ class TestAxisNotMAXDIMS(_DeprecationTestCase): def test_deprecated(self): a = np.zeros((1,)*32) self.assert_deprecated(lambda: np.repeat(a, 1, axis=np.MAXDIMS)) + + +class TestLoadtxtParseIntsViaFloat(_DeprecationTestCase): + message = r"loadtxt\(\): Parsing an integer via a float is deprecated.*" + + @pytest.mark.parametrize("dtype", np.typecodes["AllInteger"]) + def test_deprecated_warning(self, dtype): + with pytest.warns(DeprecationWarning, match=self.message): + np.loadtxt(["10.5"], dtype=dtype) + + @pytest.mark.parametrize("dtype", np.typecodes["AllInteger"]) + def test_deprecated_raised(self, dtype): + # The DeprecationWarning is chained when raised, so test manually: + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + try: + np.loadtxt(["10.5"], dtype=dtype) + except ValueError as e: + assert isinstance(e.__cause__, DeprecationWarning) |