Skip to content

Commit

Permalink
API: Disallow strings in logical ufuncs (numpy#21024)
Browse files Browse the repository at this point in the history
* 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 numpygh-20898

Co-authored-by: Ross Barnowski <rossbar@berkeley.edu>
  • Loading branch information
2 people authored and charris committed Mar 2, 2022
1 parent 593cc07 commit 87dc319
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 1 deletion.
5 changes: 5 additions & 0 deletions numpy/core/src/umath/dispatching.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion numpy/core/tests/test_ufunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down

0 comments on commit 87dc319

Please sign in to comment.