From 87dc319935a8ae288458cac5f5b69944ee01aefe Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Wed, 9 Feb 2022 14:16:40 -0800 Subject: [PATCH] API: Disallow strings in logical ufuncs (#21024) * API: Disallow strings in logical ufuncs This restores pre 1.22 NumPy behaviour for strings passed into logical ufuncs. Logical ufuncs should be able to cast inputs to booleans without modifying their results. Thus they do this now (in NumPy 1.22+). The problem is that string to bool casts are very strange in NumPy currently. This should be reverted/removed once string to bool casts are well defined. But until then, it seems more reasonable to just reject strings. Closes gh-20898 Co-authored-by: Ross Barnowski --- numpy/core/src/umath/dispatching.c | 5 +++++ numpy/core/tests/test_ufunc.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/numpy/core/src/umath/dispatching.c b/numpy/core/src/umath/dispatching.c index 81d47a0e1520..4495a1c02238 100644 --- a/numpy/core/src/umath/dispatching.c +++ b/numpy/core/src/umath/dispatching.c @@ -1018,6 +1018,11 @@ logical_ufunc_promoter(PyUFuncObject *NPY_UNUSED(ufunc), /* bail out, this is _only_ to give future/deprecation warning! */ return -1; } + if ((op_dtypes[0] != NULL && PyTypeNum_ISSTRING(op_dtypes[0]->type_num)) + || PyTypeNum_ISSTRING(op_dtypes[1]->type_num)) { + /* bail out on strings: currently casting them to bool is too weird */ + return -1; + } for (int i = 0; i < 3; i++) { PyArray_DTypeMeta *item; diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 5328acea863a..6c56e631590e 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -2134,7 +2134,7 @@ def test_logical_ufuncs_mixed_object_signatures(self, ufunc, signature): [np.logical_and, np.logical_or, np.logical_xor]) def test_logical_ufuncs_support_anything(self, ufunc): # The logical ufuncs support even input that can't be promoted: - a = np.array('1') + a = np.array(b'1', dtype="V3") c = np.array([1., 2.]) assert_array_equal(ufunc(a, c), ufunc([True, True], True)) assert ufunc.reduce(a) == True @@ -2150,6 +2150,23 @@ def test_logical_ufuncs_support_anything(self, ufunc): out = np.zeros((), dtype=a.dtype) assert ufunc.reduce(a, out=out) == 1 + @pytest.mark.parametrize("ufunc", + [np.logical_and, np.logical_or, np.logical_xor]) + def test_logical_ufuncs_reject_string(self, ufunc): + """ + Logical ufuncs are normally well defined by working with the boolean + equivalent, i.e. casting all inputs to bools should work. + + However, casting strings to bools is *currently* weird, because it + actually uses `bool(int(str))`. Thus we explicitly reject strings. + This test should succeed (and can probably just be removed) as soon as + string to bool casts are well defined in NumPy. + """ + with pytest.raises(TypeError, match="contain a loop with signature"): + ufunc(["1"], ["3"]) + with pytest.raises(TypeError, match="contain a loop with signature"): + ufunc.reduce(["1", "2", "0"]) + @pytest.mark.parametrize("ufunc", [np.logical_and, np.logical_or, np.logical_xor]) def test_logical_ufuncs_out_cast_check(self, ufunc):