@@ -194,15 +194,30 @@ find_binary_operation_path(
194
194
PyLong_CheckExact (other ) ||
195
195
PyFloat_CheckExact (other ) ||
196
196
PyComplex_CheckExact (other ) ||
197
- PyBool_Check (other )) {
197
+ PyBool_Check (other ) ||
198
+ PyArray_Check (other )) {
198
199
/*
199
200
* The other operand is ready for the operation already. Must pass on
200
201
* on float/long/complex mainly for weak promotion (NEP 50).
201
202
*/
202
- Py_INCREF (other );
203
- * other_op = other ;
203
+ * other_op = Py_NewRef (other );
204
204
return 0 ;
205
205
}
206
+ /*
207
+ * If other has __array_ufunc__ always use ufunc. If array-ufunc was None
208
+ * we already deferred. And any custom object with array-ufunc cannot call
209
+ * our ufuncs without preventing recursion.
210
+ * It may be nice to avoid double lookup in `BINOP_GIVE_UP_IF_NEEDED`.
211
+ */
212
+ PyObject * attr = PyArray_LookupSpecial (other , npy_interned_str .array_ufunc );
213
+ if (attr != NULL ) {
214
+ Py_DECREF (attr );
215
+ * other_op = Py_NewRef (other );
216
+ return 0 ;
217
+ }
218
+ else if (PyErr_Occurred ()) {
219
+ PyErr_Clear (); /* TODO[gh-14801]: propagate crashes during attribute access? */
220
+ }
206
221
207
222
/*
208
223
* Now check `other`. We want to know whether it is an object scalar
@@ -216,7 +231,13 @@ find_binary_operation_path(
216
231
}
217
232
218
233
if (!was_scalar || PyArray_DESCR (arr )-> type_num != NPY_OBJECT ) {
219
- /* The array is OK for usage and we can simply forward it
234
+ /*
235
+ * The array is OK for usage and we can simply forward it. There
236
+ * is a theoretical subtlety here: If the other object implements
237
+ * `__array_wrap__`, we may ignore that. However, this only matters
238
+ * if the other object has the identical `__array_priority__` and
239
+ * additionally already deferred back to us.
240
+ * (`obj + scalar` and `scalar + obj` are not symmetric.)
220
241
*
221
242
* NOTE: Future NumPy may need to distinguish scalars here, one option
222
243
* could be marking the array.
0 commit comments