10000 API: Do not consider subclasses for NEP 50 weak promotion by seberg · Pull Request #26905 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

API: Do not consider subclasses for NEP 50 weak promotion #26905

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

Merged
merged 2 commits into from
Jul 25, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions numpy/_core/src/multiarray/abstractdtypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,23 @@ static inline int
npy_mark_tmp_array_if_pyscalar(
PyObject *obj, PyArrayObject *arr, PyArray_DTypeMeta **dtype)
{
/*
* We check the array dtype for two reasons: First, booleans are
* integer subclasses. Second, an int, float, or complex could have
* a custom DType registered, and then we should use that.
* Further, `np.float64` is a double subclass, so must reject it.
*/
// TODO,NOTE: This function should be changed to do exact long checks
// For NumPy 2.1!
if (PyLong_Check(obj)
&& (PyArra 10000 y_ISINTEGER(arr) || PyArray_ISOBJECT(arr))) {
if (PyLong_CheckExact(obj)) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_INT;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyLongDType);
Py_SETREF(*dtype, &PyArray_PyLongDType);
}
return 1;
}
else if (PyFloat_Check(obj) && !PyArray_IsScalar(obj, Double)
&& PyArray_TYPE(arr) == NPY_DOUBLE) {
else if (PyFloat_CheckExact(obj)) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_FLOAT;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyFloatDType);
Py_SETREF(*dtype, &PyArray_PyFloatDType);
}
return 1;
}
else if (PyComplex_Check(obj) && !PyArray_IsScalar(obj, CDouble)
&& PyArray_TYPE(arr) == NPY_CDOUBLE) {
else if (PyComplex_CheckExact(obj)) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_COMPLEX;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyComplexDType);
Expand Down
6 changes: 3 additions & 3 deletions numpy/_core/src/multiarray/scalartypes.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ find_binary_operation_path(
*self_op = NULL;

if (PyArray_IsScalar(other, Generic) ||
PyLong_Check(other) ||
PyFloat_Check(other) ||
PyComplex_Check(other) ||
PyLong_CheckExact(other) ||
PyFloat_CheckExact(other) ||
PyComplex_CheckExact(other) ||
PyBool_Check(other)) {
/*
* The other operand is ready for the operation already. Must pass on
Expand Down
30 changes: 6 additions & 24 deletions numpy/_core/src/umath/scalarmath.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -954,15 +954,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
return CONVERSION_SUCCESS;
}

if (PyFloat_Check(value)) {
if (!PyFloat_CheckExact(value)) {
/* A NumPy double is a float subclass, but special. */
if (PyArray_IsScalar(value, Double)) {
descr = PyArray_DescrFromType(NPY_DOUBLE);
goto numpy_scalar;
}
*may_need_deferring = NPY_TRUE;
}
if (PyFloat_CheckExact(value)) {
if (!IS_SAFE(NPY_DOUBLE, NPY_@TYPE@)) {
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
/* Legacy promotion and weak-and-warn not handled here */
Expand All @@ -978,10 +970,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
return CONVERSION_SUCCESS;
}

if (PyLong_Check(value)) {
if (!PyLong_CheckExact(value)) {
*may_need_deferring = NPY_TRUE;
}
if (PyLong_CheckExact(value)) {
if (!IS_SAFE(NPY_LONG, NPY_@TYPE@)) {
/*
* long -> (c)longdouble is safe, so `OTHER_IS_UNKNOWN_OBJECT` will
Expand Down Expand Up @@ -1009,15 +998,7 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
return CONVERSION_SUCCESS;
}

if (PyComplex_Check(value)) {
if (!PyComplex_CheckExact(value)) {
/* A NumPy complex double is a float subclass, but special. */
if (PyArray_IsScalar(value, CDouble)) {
descr = PyArray_DescrFromType(NPY_CDOUBLE);
goto numpy_scalar;
}
*may_need_deferring = NPY_TRUE;
}
if (PyComplex_CheckExact(value)) {
if (!IS_SAFE(NPY_CDOUBLE, NPY_@TYPE@)) {
if (get_npy_promotion_state() != NPY_USE_WEAK_PROMOTION) {
/* Legacy promotion and weak-and-warn not handled here */
Expand Down Expand Up @@ -1079,7 +1060,6 @@ convert_to_@name@(PyObject *value, @type@ *result, npy_bool *may_need_deferring)
return OTHER_IS_UNKNOWN_OBJECT;
}

numpy_scalar:
if (descr->typeobj != Py_TYPE(value)) {
/*
* This is a subclass of a builtin type, we may continue normally,
Expand Down Expand Up @@ -1409,7 +1389,8 @@ static PyObject *
npy_bool may_need_deferring;
conversion_result res = convert_to_@name@(
other, &other_val_conv, &may_need_deferring);
other_val = other_val_conv; /* Need a float value */
/* Actual float cast `other_val` is set below on success. */

if (res == CONVERSION_ERROR) {
return NULL; /* an error occurred (should never happen) */
}
Expand All @@ -1420,6 +1401,7 @@ static PyObject *
case DEFER_TO_OTHER_KNOWN_SCALAR:
Py_RETURN_NOTIMPLEMENTED;
case CONVERSION_SUCCESS:
other_val = other_val_conv; /* Need a float value */
break; /* successfully extracted value we can proceed */
case OTHER_IS_UNKNOWN_OBJECT:
case PROMOTION_REQUIRED:
Expand Down
19 changes: 13 additions & 6 deletions numpy/_core/tests/test_scalarmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,9 @@ def test_longdouble_complex():
@pytest.mark.parametrize("subtype", [float, int, complex, np.float16])
@np._no_nep50_warning()
def test_pyscalar_subclasses(subtype, __op__, __rop__, op, cmp):
# This tests that python scalar subclasses behave like a float64 (if they
# don't override it).
# In an earlier version of NEP 50, they behaved like the Python buildins.
def op_func(self, other):
return __op__

Expand All @@ -1095,25 +1098,29 @@ def rop_func(self, other):

# When no deferring is indicated, subclasses are handled normally.
myt = type("myt", (subtype,), {__rop__: rop_func})
behaves_like = lambda x: np.array(subtype(x))[()]

# Check for float32, as a float subclass float64 may behave differently
res = op(myt(1), np.float16(2))
expected = op(subtype(1), np.float16(2))
expected = op(behaves_like(1), np.float16(2))
assert res == expected
assert type(res) == type(expected)
res = op(np.float32(2), myt(1))
expected = op(np.float32(2), subtype(1))
expected = op(np.float32(2), behaves_like(1))
assert res == expected
assert type(res) == type(expected)

# Same check for longdouble:
# Same check for longdouble (compare via dtype to accept float64 when
# longdouble has the identical size), which is currently not perfectly
# consistent.
res = op(myt(1), np.longdouble(2))
expected = op(subtype(1), np.longdouble(2))
expected = op(behaves_like(1), np.longdouble(2))
assert res == expected
assert type(res) == type(expected)
assert np.dtype(type(res)) == np.dtype(type(expected))
res = op(np.float32(2), myt(1))
expected = op(np.longdouble(2), subtype(1))
expected = op(np.float32(2), behaves_like(1))
assert res == expected
assert np.dtype(type(res)) == np.dtype(type(expected))


def test_truediv_int():
Expand Down < 4381 /td>
Loading
0