Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEP: Expire deprecation to ignore bad dtype= in logical ufuncs #22541

Merged
merged 3 commits into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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