diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2021-10-21 16:31:18 -0500 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2021-10-21 16:31:18 -0500 |
commit | fb6a4a69526c69d1f995cfa50105a3b6b2b38a9d (patch) | |
tree | c116bae3166d4d238ecd0a730b3f235b032065ff | |
parent | ba3664766d1f8c6b2521b82edd717a2f2be974f2 (diff) | |
download | numpy-fb6a4a69526c69d1f995cfa50105a3b6b2b38a9d.tar.gz |
BUG: Relax homogeneous signature fallback in type resolution
Relaxing the fallback means that reductions, which pass
`(dtype, None, dtype)` as signature (`type_tup` in the old resolver)
can resolve to the homogeneous loop if `dtype=dtype` is passed.
This change is important, because the resolution changed from using
`(None, None, dtype)` to `(dtype, None, dtype)` since reductions
normally are expected to have the first input match the output.
Specifying the signature more precisly without also relaxing the
"homogeneous" fallback, however, lead to a regression noticed
by Pandas.
Closes gh-20151
-rw-r--r-- | numpy/core/src/umath/ufunc_type_resolution.c | 11 | ||||
-rw-r--r-- | numpy/core/tests/test_ufunc.py | 5 |
2 files changed, 10 insertions, 6 deletions
diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index aa8d7b982..9ed923cf5 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -2164,6 +2164,10 @@ type_tuple_type_resolver(PyUFuncObject *self, * `signature=(None,)*nin + (dtype,)*nout`. If the signature matches that * exactly (could be relaxed but that is not necessary for backcompat), * we also try `signature=(dtype,)*(nin+nout)`. + * Since reduction pass in `(dtype, None, dtype)` we broaden this to + * replacing all unspecified dtypes with the homogeneous output one. + * Note that this can (and often will) lead to unsafe casting. This is + * normally rejected (but not currently for reductions!). * This used to be the main meaning for `dtype=dtype`, but some calls broke * the expectation, and changing it allows for `dtype=dtype` to be useful * for ufuncs like `np.ldexp` in the future while also normalizing it to @@ -2182,13 +2186,12 @@ type_tuple_type_resolver(PyUFuncObject *self, if (homogeneous_type != NPY_NOTYPE) { for (int i = 0; i < nin; i++) { if (specified_types[i] != NPY_NOTYPE) { - homogeneous_type = NPY_NOTYPE; - break; + /* Never replace a specified type! */ + continue; } specified_types[i] = homogeneous_type; } - } - if (homogeneous_type != NPY_NOTYPE) { + /* Try again with the homogeneous specified types. */ res = type_tuple_type_resolver_core(self, op, input_casting, casting, specified_types, any_object, diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index 78833a33c..4b06c8668 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -2383,8 +2383,9 @@ def test_reduce_casterrors(offset): out = np.array(-1, dtype=np.intp) count = sys.getrefcount(value) - with pytest.raises(TypeError): - # This is an unsafe cast, but we currently always allow that: + with pytest.raises(ValueError, match="invalid literal"): + # This is an unsafe cast, but we currently always allow that. + # Note that the double loop is picked, but the cast fails. np.add.reduce(arr, dtype=np.intp, out=out) assert count == sys.getrefcount(value) # If an error occurred during casting, the operation is done at most until |