diff --git a/doc/release/upcoming_changes/29052.deprecation.rst b/doc/release/upcoming_changes/29052.deprecation.rst new file mode 100644 index 000000000000..e302907abfba --- /dev/null +++ b/doc/release/upcoming_changes/29052.deprecation.rst @@ -0,0 +1,10 @@ +Positional ``out`` argument to `np.maximum`, `np.minimum` is deprecated +----------------------------------------------------------------------- +Passing the output array ``out`` positionally to `numpy.maximum` and +`numpy.minimum` is deprecated. For example, ``np.maximum(a, b, c)`` will +emit a deprecation warning, since ``c`` is treated as the output buffer +rather than a third input. + +Always pass the output with the keyword form, e.g. +``np.maximum(a, b, out=c)``. This makes intent clear and simplifies +type annotations. diff --git a/numpy/_core/src/umath/ufunc_object.c b/numpy/_core/src/umath/ufunc_object.c index 1d2c3edbd3b9..6b8cc1789f94 100644 --- a/numpy/_core/src/umath/ufunc_object.c +++ b/numpy/_core/src/umath/ufunc_object.c @@ -65,6 +65,7 @@ #include "mapping.h" #include "npy_static_data.h" #include "multiarraymodule.h" +#include "number.h" /********** PRINTF DEBUG TRACING **************/ #define NPY_UF_DBG_TRACING 0 @@ -4368,6 +4369,21 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc, Py_INCREF(tmp); PyTuple_SET_ITEM(full_args.out, i-nin, tmp); } + + /* Extra positional args but no keywords */ + /* DEPRECATED NumPy 2.4, 2025-08 */ + if ((PyObject *)ufunc == n_ops.maximum || (PyObject *)ufunc == n_ops.minimum) { + + if (DEPRECATE( + "Passing more than 2 positional arguments to np.maximum and np.minimum " + "is deprecated. If you meant to use the third argument as an output, " + "use the `out` keyword argument instead. If you hoped to work with " + "more than 2 inputs, combine them into a single array and get the extrema " + "for the relevant axis.") < 0) { + return NULL; + } + } + if (all_none) { Py_SETREF(full_args.out, NULL); } diff --git a/numpy/_core/tests/test_deprecations.py b/numpy/_core/tests/test_deprecations.py index 9be253f7c95c..252189ef321a 100644 --- a/numpy/_core/tests/test_deprecations.py +++ b/numpy/_core/tests/test_deprecations.py @@ -535,3 +535,12 @@ def use_suppress_warnings(): sup.filter(RuntimeWarning, 'invalid value encountered in divide') self.assert_deprecated(use_suppress_warnings) + + +class TestTooManyArgsExtremum(_DeprecationTestCase): + # Deprecated in Numpy 2.4, 2025-08, gh-27639 + message = "Passing more than 2 positional arguments to np.maximum and np.minimum " + + @pytest.mark.parametrize("ufunc", [np.minimum, np.maximum]) + def test_extremem_3_args(self, ufunc): + self.assert_deprecated(ufunc, args=(np.ones(1), np.zeros(1), np.empty(1)))