diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/src/umath/scalarmath.c.src | 54 | ||||
-rw-r--r-- | numpy/core/tests/test_scalarmath.py | 31 |
2 files changed, 34 insertions, 51 deletions
diff --git a/numpy/core/src/umath/scalarmath.c.src b/numpy/core/src/umath/scalarmath.c.src index 15afaae00..f273cdd91 100644 --- a/numpy/core/src/umath/scalarmath.c.src +++ b/numpy/core/src/umath/scalarmath.c.src @@ -657,9 +657,17 @@ static NPY_INLINE int * * So we have multiple possible paths: * - The other scalar is a subclass. In principle *both* inputs could be - * different subclasses. In this case we should defer, but only if the - * other subclass does *not* inherit the slot as well. - * That check is included via `BINOP_IS_FORWARD` (or manually for comparison). + * different subclasses. In this case it would make sense to defer, but + * Python's `int` does not try this as well, so we do not here: + * + * class A(int): pass + * class B(int): + * def __add__(self, other): return "b" + * __radd__ = __add__ + * + * A(1) + B(1) # return 2 + * B(1) + A(1) # return "b" + * * - The other scalar can be converted: All is good, we do the operation * - The other scalar cannot be converted, there are two possibilities: * - The reverse should work, so we return NotImplemented to defer. @@ -757,8 +765,8 @@ typedef enum { */ PROMOTION_REQUIRED, /* - * The other object may be a subclass, conversion is successful, but we - * may still need to defer. + * The other object may be a subclass, conversion is successful. We do + * not special case this as Python's `int` does not either */ OTHER_IS_SUBCLASS, } conversion_result; @@ -882,6 +890,10 @@ convert_to_@name@(PyObject *value, @type@ *result) /* Optimize the identical scalar specifically. */ if (PyArray_IsScalar(value, @Name@)) { *result = PyArrayScalar_VAL(value, @Name@); + /* + * In principle special, assyemetric, handling could be possible for + * subclasses. But in practice even we do not bother later. + */ return OTHER_IS_SUBCLASS; } @@ -1112,13 +1124,11 @@ static PyObject * return PyGenericArrType_Type.tp_as_number->nb_@oper@(a,b); case OTHER_IS_SUBCLASS: /* - * Success, but defer if not a forward operator. This implements - * the asymmetry of `__add__` and `__radd__` for subclassing. - * (A subclass replacing `__add__`/`__radd__` has precedence.) + * Success converting. We _could_ in principle defer in cases + * were the other subclass does not inherit the behavior. In + * practice not even Python's `int` attempt this, so we also punt. */ - if (is_forward && BINOP_IS_FORWARD(a, b, nb_@oper@, @name@_@oper@)) { - Py_RETURN_NOTIMPLEMENTED; - } + break; } #if @fperr@ @@ -1278,13 +1288,11 @@ static PyObject * return PyGenericArrType_Type.tp_as_number->nb_power(a, b, modulo); case OTHER_IS_SUBCLASS: /* - * Success, but defer if not a forward operator. This implements - * the asymmetry of `__add__` and `__radd__` for subclassing. - * (A subclass replacing `__add__`/`__radd__` has precedence.) + * Success converting. We _could_ in principle defer in cases + * were the other subclass does not inherit the behavior. In + * practice not even Python's `int` attempt this, so we also punt. */ - if (is_forward && BINOP_IS_FORWARD(a, b, nb_power, @name@_power)) { - Py_RETURN_NOTIMPLEMENTED; - } + break; } #if !@isint@ @@ -1653,14 +1661,12 @@ static PyObject* return PyGenericArrType_Type.tp_richcompare(self, other, cmp_op); case OTHER_IS_SUBCLASS: /* - * Success, but defer if not a forward operator. This implements - * the asymmetry of `__add__` and `__radd__` for subclassing. - * (A subclass replacing `__add__`/`__radd__` has precedence. It - * also means that such a subclass has to always take charge.) + * Success converting. We _could_ in principle defer in cases + * were the other subclass does not inherit the behavior. In + * practice not even Python's `int` attempt this, so we also punt. + * (This is also even trickier for richcompare, though.) */ - if ((void *)Py_TYPE(other)->tp_richcompare != (void *)@name@_richcompare) { - Py_RETURN_NOTIMPLEMENTED; - } + break; } arg1 = PyArrayScalar_VAL(self, @Name@); diff --git a/numpy/core/tests/test_scalarmath.py b/numpy/core/tests/test_scalarmath.py index 527106973..a0de788a7 100644 --- a/numpy/core/tests/test_scalarmath.py +++ b/numpy/core/tests/test_scalarmath.py @@ -950,9 +950,6 @@ ops_with_names = [ @pytest.mark.parametrize(["__op__", "__rop__", "op", "cmp"], ops_with_names) @pytest.mark.parametrize("sctype", [np.float32, np.float64, np.longdouble]) -@pytest.mark.xfail(IS_PYPY, - reason="PyPy does not replace `tp_richcompare` for subclasses so" - "our we do not realize when deferring should be preferred.") def test_subclass_deferral(sctype, __op__, __rop__, op, cmp): """ This test covers scalar subclass deferral. Note that this is exceedingly @@ -980,36 +977,16 @@ def test_subclass_deferral(sctype, __op__, __rop__, op, cmp): return __rop__ myf_op = type("myf_op", (sctype,), {__op__: op_func, __rop__: rop_func}) - # Note: __rop__ first, so defer "wins" if __rop__ == __op__ - myf_defer = type("myf_defer", (sctype,), {__rop__: rop_func, __op__: defer}) # inheritance has to override, or this is correctly lost: res = op(myf_simple1(1), myf_simple2(2)) assert type(res) == sctype or type(res) == np.bool_ - assert myf_simple1(1) + myf_simple2(2) == 3 + assert op(myf_simple1(1), myf_simple2(2)) == op(1, 2) # inherited - # When a subclass overrides though, we should always give it a chance, - # even for the __rop__! + # Two independent subclasses do not really define an order. This could + # be attempted, but we do not since Python's `int` does neither: assert op(myf_op(1), myf_simple1(2)) == __op__ - assert op(myf_simple1(1), myf_op(2)) == __rop__ - - # If the other class defers back to NumPy, we should attempt the operation - # NOTE: this path goes through the generic array/ufunc path! - # Note that for comparisons, the class is badly behaved since we cannot - # know if an operation is "reversed". - if cmp and op in [operator.eq, operator.ne]: - # Undefined equality leads to False and True - assert op(myf_defer(1), myf_simple1(2)) == op(None, 1) - # since __op__ == __rop__ here, it gives the same result: - assert op(myf_simple1(2), myf_defer(1)) == op(None, 1) - elif cmp: - with pytest.raises(TypeError): - op(myf_defer(1), myf_simple1(2)) - assert op(myf_simple1(2), myf_defer(1)) == __rop__ - else: - res = op(myf_defer(1), myf_simple1(2)) - assert type(res) == sctype - assert op(myf_simple1(2), myf_defer(1)) == __rop__ + assert op(myf_simple1(1), myf_op(2)) == op(1, 2) # inherited @pytest.mark.parametrize(["__op__", "__rop__", "op", "cmp"], ops_with_names) |