@@ -523,38 +523,6 @@ PyUFunc_GetPyValues(char *name, int *bufsize, int *errmask, PyObject **errobj)
523
523
return _extract_pyvals (ref , name , bufsize , errmask , errobj );
524
524
}
525
525
526
- #define GETATTR (str , rstr ) do {if (strcmp(name, #str) == 0) \
527
- return PyObject_HasAttrString(op, "__" #rstr "__");} while (0);
528
-
529
- static int
530
- _has_reflected_op (PyObject * op , const char * name )
531
- {
532
- GETATTR (add , radd );
533
- GETATTR (subtract , rsub );
534
- GETATTR (multiply , rmul );
535
- GETATTR (divide , rdiv );
536
- GETATTR (true_divide , rtruediv );
537
- GETATTR (floor_divide , rfloordiv );
538
- GETATTR (remainder , rmod );
539
- GETATTR (power , rpow );
540
- GETATTR (left_shift , rlshift );
541
- GETATTR (right_shift , rrshift );
542
- GETATTR (bitwise_and , rand );
543
- GETATTR (bitwise_xor , rxor );
544
- GETATTR (bitwise_or , ror );
545
- /* Comparisons */
546
- GETATTR (equal , eq );
547
- GETATTR (not_equal , ne );
548
- GETATTR (greater , lt );
549
- GETATTR (less , gt );
550
- GETATTR (greater_equal , le );
551
- GETATTR (less_equal , ge );
552
- return 0 ;
553
- }
554
-
555
- #undef GETATTR
556
-
557
-
558
526
/* Return the position of next non-white-space char in the string */
559
527
static int
560
528
_next_non_white_space (const char * str , int offset )
@@ -896,16 +864,122 @@ get_ufunc_arguments(PyUFuncObject *ufunc,
896
864
}
897
865
}
898
866
899
- /*
900
- * Indicate not implemented if there are flexible objects (structured
901
- * type or string) but no object types and no registered struct
902
- * dtype ufuncs.
903
- *
904
- * Not sure - adding this increased to 246 errors, 150 failures.
905
- */
906
867
if (any_flexible && !any_flexible_userloops && !any_object ) {
907
- return -2 ;
908
-
868
+ /* Traditionally, we return -2 here (meaning "NotImplemented") anytime
869
+ * we hit the above condition.
870
+ *
871
+ * This condition basically means "we are doomed", b/c the "flexible"
872
+ * dtypes -- strings and void -- cannot have their own ufunc loops
873
+ * registered (except via the special "flexible userloops" mechanism),
874
+ * and they can't be cast to anything except object (and we only cast
875
+ * to object if any_object is true). So really we should do nothing
876
+ * here and continue and let the proper error be raised. But, we can't
877
+ * quite yet, b/c of backcompat.
878
+ *
879
+ * Most of the time, this NotImplemented either got returned directly
880
+ * to the user (who can't do anything useful with it), or got passed
881
+ * back out of a special function like __mul__. And fortunately, for
882
+ * almost all special functions, the end result of this was a
883
+ * TypeError. Which is also what we get if we just continue without
884
+ * this special case, so this special case is unnecessary.
885
+ *
886
+ * The only thing that actually depended on the NotImplemented is
887
+ * array_richcompare, which did two things with it. First, it needed
888
+ * to see this NotImplemented in order to implement the special-case
889
+ * comparisons for
890
+ *
891
+ * string < <= == != >= > string
892
+ * void == != void
893
+ *
894
+ * Now it checks for those cases first, before trying to call the
895
+ * ufunc, so that's no problem. What it doesn't handle, though, is
896
+ * cases like
897
+ *
898
+ * float < string
899
+ *
900
+ * or
901
+ *
902
+ * float == void
903
+ *
904
+ * For those, it just let the NotImplemented bubble out, and accepted
905
+ * Python's default handling. And unfortunately, for comparisons,
906
+ * Python's default is *not* to raise an error. Instead, it returns
907
+ * something that depends on the operator:
908
+ *
909
+ * == return False
910
+ * != return True
911
+ * < <= >= > Python 2: use "fallback" (= weird and broken) ordering
912
+ * Python 3: raise TypeError (hallelujah)
913
+ *
914
+ * In most cases this is straightforwardly broken, because comparison
915
+ * of two arrays should always return an array, and here we end up
916
+ * returning a scalar. However, there is an exception: if we are
917
+ * comparing two scalars for equality, then it actually is correct to
918
+ * return a scalar bool instead of raising an error. If we just
919
+ * removed this special check entirely, then "np.float64(1) == 'foo'"
920
+ * would raise an error instead of returning False, which is genuinely
921
+ * wrong.
922
+ *
923
+ * The proper end goal here is:
924
+ * 1) == and != should be implemented in a proper vectorized way for
925
+ * all types. The short-term hack for this is just to add a
926
+ * special case to PyUFunc_DefaultLegacyInnerLoopSelector where
927
+ * if it can't find a comparison loop for the given types, and
928
+ * the ufunc is np.equal or np.not_equal, then it returns a loop
929
+ * that just fills the output array with False (resp. True). Then
930
+ * array_richcompare could trust that whenever its special cases
931
+ * don't apply, simply calling the ufunc will do the right thing,
932
+ * even without this special check.
933
+ * 2) < <= >= > should raise an error if no comparison function can
934
+ * be found. array_richcompare already handles all string <>
935
+ * string cases, and void dtypes don't have ordering, so again
936
+ * this would mean that array_richcompare could simply call the
937
+ * ufunc and it would do the right thing (i.e., raise an error),
938
+ * again without needing this special check.
939
+ *
940
+ * So this means that for the transition period, our goal is:
941
+ * == and != on scalars should simply return NotImplemented like
942
+ * they always did, since everything ends up working out correctly
943
+ * in this case only
944
+ * == and != on arrays should issue a FutureWarning and then return
945
+ * NotImplemented
946
+ * < <= >= > on all flexible dtypes on py2 should raise a
947
+ * DeprecationWarning, and then return NotImplemented. On py3 we
948
+ * skip the warning, though, b/c it would just be immediately be
949
+ * followed by an exception anyway.
950
+ *
951
+ * And for all other operations, we let things continue as normal.
952
+ */
953
+ /* strcmp() is a hack but I think we can get away with it for this
954
+ * temporary measure.
955
+ */
956
+ if (!strcmp (ufunc_name , "equal" )
957
+ || !strcmp (ufunc_name , "not_equal" )) {
958
+ /* Warn on non-scalar, return NotImplemented regardless */
959
+ assert (nin == 2 );
960
+ if (PyArray_NDIM (out_op [0 ]) != 0
961
+ || PyArray_NDIM (out_op [1 ]) != 0 ) {
962
+ if (DEPRECATE_FUTUREWARNING (
963
+ "elementwise comparison failed; returning scalar "
964
+ "but in the future will perform elementwise "
965
+ "comparison" ) < 0 ) {
966
+ return -1 ;
967
+ }
968
+ }
969
+ return -2 ;
970
+ }
971
+ else if (!strcmp (ufunc_name , "less" )
972
+ || !strcmp (ufunc_name , "less_equal" )
973
+ || !strcmp (ufunc_name , "greater" )
974
+ || !strcmp (ufunc_name , "greater_equal" )) {
975
+ #if !defined(NPY_PY3K )
976
+ if (DEPRECATE ("unorderable dtypes; returning scalar but in "
977
+ "the future this will be an error" ) < 0 ) {
978
+ return -1 ;
979
+ }
980
+ #endif
981
+ return -2 ;
982
+ }
909
983
}
910
984
911
985
/* Get positional output arguments */
@@ -2142,26 +2216,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
2142
2216
goto fail ;
2143
2217
}
2144
2218
2145
- /*
2146
- * FAIL with NotImplemented if the other object has
2147
- * the __r<op>__ method and has a higher priority than
2148
- * the current op (signalling it can handle ndarray's).
2149
- */
2150
- if (nin == 2 && nout == 1 && dtypes [1 ]-> type_num == NPY_OBJECT ) {
2151
- PyObject * _obj = PyTuple_GET_ITEM (args , 1 );
2152
- if (!PyArray_CheckExact (_obj )) {
2153
- double self_prio , other_prio ;
2154
- self_prio = PyArray_GetPriority (PyTuple_GET_ITEM (args , 0 ),
2155
- NPY_SCALAR_PRIORITY );
2156
- other_prio = PyArray_GetPriority (_obj , NPY_SCALAR_PRIORITY );
2157
- if (self_prio < other_prio &&
2158
- _has_reflected_op (_obj , ufunc_name )) {
2159
- retval = -2 ;
2160
- goto fail ;
2161
- }
2162
- }
2163
- }
2164
-
2165
2219
#if NPY_UF_DBG_TRACING
2166
2220
printf ("input types:\n" );
2167
2221
for (i = 0 ; i < nin ; ++ i ) {
@@ -2521,27 +2575,6 @@ PyUFunc_GenericFunction(PyUFuncObject *ufunc,
2521
2575
}
2522
2576
}
2523
2577
2524
- /*
2525
- * FAIL with NotImplemented if the other object has
2526
- * the __r<op>__ method and has __array_priority__ as
2527
- * an attribute (signalling it can handle ndarray's)
2528
- * and is not already an ndarray or a subtype of the same type.
2529
- */
2530
- if (nin == 2 && nout == 1 && dtypes [1 ]-> type_num == NPY_OBJECT ) {
2531
- PyObject * _obj = PyTuple_GET_ITEM (args , 1 );
2532
- if (!PyArray_Check (_obj )) {
2533
- double self_prio , other_prio ;
2534
- self_prio = PyArray_GetPriority (PyTuple_GET_ITEM (args , 0 ),
2535
- NPY_SCALAR_PRIORITY );
2536
- other_prio = PyArray_GetPriority (_obj , NPY_SCALAR_PRIORITY );
2537
- if (self_prio < other_prio &&
2538
- _has_re
D1C2
flected_op (_obj , ufunc_name )) {
2539
- retval = -2 ;
2540
- goto fail ;
2541
- }
2542
- }
2543
- }
2544
-
2545
2578
#if NPY_UF_DBG_TRACING
2546
2579
printf ("input types:\n" );
2547
2580
for (i = 0 ; i < nin ; ++ i ) {
@@ -4222,13 +4255,15 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds)
4222
4255
return NULL ;
4223
4256
}
4224
4257
else if (ufunc -> nin == 2 && ufunc -> nout == 1 ) {
4225
- /* To allow the other argument to be given a chance */
4258
+ /* For array_richcompare's benefit -- see the long comment in
4259
+ * get_ufunc_arguments.
4260
+ */
4226
4261
Py_INCREF (Py_NotImplemented );
4227
4262
return Py_NotImplemented ;
4228
4263
}
4229
4264
else {
4230
4265
PyErr_SetString (PyExc_TypeError ,
4231
- "Not implemented for this type " );
4266
+ "XX can't happen, please report a bug XX " );
4232
4267
return NULL ;
4233
4268
}
4234
4269
}
0 commit comments