Skip to content

Commit

Permalink
DEP: Expire deprecation to ignore bad dtype= in logical ufuncs (#22541)
Browse files Browse the repository at this point in the history
* DEP: Expire deprecation to ignore bad dtype= in logical ufuncs

Basically only `bool` and `object dtype make sense, but they did
not work correctly.  The dtype argument was rather ignored often.

The offending behavior was deprecated in 1.20 and is now removed.

Co-authored-by: Sebastian Berg <sebastianb@nvidia.com>
Co-authored-by: Charles Harris <charlesr.harris@gmail.com>
  • Loading branch information
3 people committed Nov 7, 2022
1 parent 9399106 commit 10908c5
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 87 deletions.
3 changes: 3 additions & 0 deletions doc/release/upcoming_changes/22541.expired.rst
@@ -0,0 +1,3 @@
* The ``dtype=`` argument to comparison ufuncs is now applied
correctly. That means that only ``bool`` and ``object`` are valid
values and ``dtype=object`` is enforced.
16 changes: 3 additions & 13 deletions numpy/core/src/umath/dispatching.c
Expand Up @@ -667,12 +667,9 @@ legacy_promote_using_legacy_type_resolver(PyUFuncObject *ufunc,
Py_DECREF(out_descrs[i]);
}
/*
* The PyUFunc_SimpleBinaryComparisonTypeResolver has a deprecation
* warning (ignoring `dtype=`) and cannot be cached.
* All datetime ones *should* have a warning, but currently don't,
* but ignore all signature passing also. So they can also
* not be cached, and they mutate the signature which of course is wrong,
* but not doing it would confuse the code later.
* datetime legacy resolvers ignore the signature, which should be
* warn/raise (when used). In such cases, the signature is (incorrectly)
* mutated, and caching is not possible.
*/
for (int i = 0; i < nargs; i++) {
if (signature[i] != NULL && signature[i] != operation_DTypes[i]) {
Expand Down Expand Up @@ -1042,13 +1039,6 @@ default_ufunc_promoter(PyUFuncObject *ufunc,
PyArray_DTypeMeta *op_dtypes[], PyArray_DTypeMeta *signature[],
PyArray_DTypeMeta *new_op_dtypes[])
{
if (ufunc->type_resolver == &PyUFunc_SimpleBinaryComparisonTypeResolver
&& signature[0] == NULL && signature[1] == NULL
&& signature[2] != NULL && signature[2]->type_num != NPY_BOOL) {
/* bail out, this is _only_ to give future/deprecation warning! */
return -1;
}

/* If nin < 2 promotion is a no-op, so it should not be registered */
assert(ufunc->nin > 1);
if (op_dtypes[0] == NULL) {
Expand Down
50 changes: 3 additions & 47 deletions numpy/core/src/umath/ufunc_type_resolution.c
Expand Up @@ -382,53 +382,9 @@ PyUFunc_SimpleBinaryComparisonTypeResolver(PyUFuncObject *ufunc,
}
}
else {
PyArray_Descr *descr;
/*
* DEPRECATED 2021-03, NumPy 1.20
*
* If the type tuple was originally a single element (probably),
* issue a deprecation warning, but otherwise accept it. Since the
* result dtype is always boolean, this is not actually valid unless it
* is `object` (but if there is an object input we already deferred).
*
* TODO: Once this deprecation is gone, the special case for
* `PyUFunc_SimpleBinaryComparisonTypeResolver` in dispatching.c
* can be removed.
*/
if (PyTuple_Check(type_tup) && PyTuple_GET_SIZE(type_tup) == 3 &&
PyTuple_GET_ITEM(type_tup, 0) == Py_None &&
PyTuple_GET_ITEM(type_tup, 1) == Py_None &&
PyArray_DescrCheck(PyTuple_GET_ITEM(type_tup, 2))) {
descr = (PyArray_Descr *)PyTuple_GET_ITEM(type_tup, 2);
if (descr->type_num == NPY_OBJECT) {
if (DEPRECATE_FUTUREWARNING(
"using `dtype=object` (or equivalent signature) will "
"return object arrays in the future also when the "
"inputs do not already have `object` dtype.") < 0) {
return -1;
}
}
else if (descr->type_num != NPY_BOOL) {
if (DEPRECATE(
"using `dtype=` in comparisons is only useful for "
"`dtype=object` (and will do nothing for bool). "
"This operation will fail in the future.") < 0) {
return -1;
}
}
}
else {
/* Usually a failure, but let the default version handle it */
return PyUFunc_DefaultTypeResolver(ufunc, casting,
operands, type_tup, out_dtypes);
}

out_dtypes[0] = NPY_DT_CALL_ensure_canonical(descr);
if (out_dtypes[0] == NULL) {
return -1;
}
out_dtypes[1] = out_dtypes[0];
Py_INCREF(out_dtypes[1]);
/* Usually a failure, but let the default version handle it */
return PyUFunc_DefaultTypeResolver(ufunc, casting,
operands, type_tup, out_dtypes);
}

/* Output type is always boolean */
Expand Down
25 changes: 0 additions & 25 deletions numpy/core/tests/test_deprecations.py
Expand Up @@ -1000,31 +1000,6 @@ def test_deprecated(self):
self.assert_deprecated(lambda: np.add(1, 2, sig=(np.dtype("l"),)))


class TestComparisonBadDType(_DeprecationTestCase):
# Deprecated 2021-04-01, NumPy 1.21
message = r"using `dtype=` in comparisons is only useful for"

def test_deprecated(self):
self.assert_deprecated(lambda: np.equal(1, 1, dtype=np.int64))
# Not an error only for the transition
self.assert_deprecated(lambda: np.equal(1, 1, sig=(None, None, "l")))

def test_not_deprecated(self):
np.equal(True, False, dtype=bool)
np.equal(3, 5, dtype=bool, casting="unsafe")
np.equal([None], [4], dtype=object)

class TestComparisonBadObjectDType(_DeprecationTestCase):
# Deprecated 2021-04-01, NumPy 1.21 (different branch of the above one)
message = r"using `dtype=object` \(or equivalent signature\) will"
warning_cls = FutureWarning

def test_deprecated(self):
self.assert_deprecated(lambda: np.equal(1, 1, dtype=object))
self.assert_deprecated(
lambda: np.equal(1, 1, sig=(None, None, object)))


class TestCtypesGetter(_DeprecationTestCase):
# Deprecated 2021-05-18, Numpy 1.21.0
warning_cls = DeprecationWarning
Expand Down
19 changes: 17 additions & 2 deletions numpy/core/tests/test_umath.py
Expand Up @@ -338,6 +338,21 @@ def test_error_in_equal_reduce(self):
assert_equal(np.equal.reduce(a, dtype=bool), True)
assert_raises(TypeError, np.equal.reduce, a)

def test_object_dtype(self):
assert np.equal(1, [1], dtype=object).dtype == object
assert np.equal(1, [1], signature=(None, None, "O")).dtype == object

def test_object_nonbool_dtype_error(self):
# bool output dtype is fine of course:
assert np.equal(1, [1], dtype=bool).dtype == bool

# but the following are examples do not have a loop:
with pytest.raises(TypeError, match="No loop matching"):
np.equal(1, 1, dtype=np.int64)

with pytest.raises(TypeError, match="No loop matching"):
np.equal(1, 1, sig=(None, None, "l"))


class TestAdd:
def test_reduce_alignment(self):
Expand Down Expand Up @@ -1095,7 +1110,7 @@ def test_integer_to_negative_power(self):
assert_raises(ValueError, np.power, a, minusone)
assert_raises(ValueError, np.power, one, b)
assert_raises(ValueError, np.power, one, minusone)

def test_float_to_inf_power(self):
for dt in [np.float32, np.float64]:
a = np.array([1, 1, 2, 2, -2, -2, np.inf, -np.inf], dt)
Expand Down Expand Up @@ -1348,7 +1363,7 @@ def test_sincos_values(self):
yf = np.array(y, dtype=dt)
assert_equal(np.sin(yf), xf)
assert_equal(np.cos(yf), xf)


with np.errstate(invalid='raise'):
for callable in [np.sin, np.cos]:
Expand Down

0 comments on commit 10908c5

Please sign in to comment.