Skip to content

ENH: show warning when np.maximum receives more than 2 inputs #29052

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 10 commits into from
Aug 20, 2025

Conversation

lvllvl
Copy link
Contributor

@lvllvl lvllvl commented May 25, 2025

attempts to resolve #27639

Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind discussing whether we should start forcing out= more generally, since the overhead of using out= is pretty low now.

But maybe we say maximum is special, which is fine:

  • You are doing twice the same thing in the same code.
  • Do not just go to an error, this should go through a DeprecationWarning. Maybe one can argue that it is almost always a bug and we go directly to a VisibleDeprecationWarning. But I am a bit skeptical, also because DeprecationWarnings are seen more these days anyway.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should happen just for maximum (though that discussion is perhaps best done in #27639).

But it certainly shouldn't be in the hot path for every ufunc -- instead, on l.4321 (original one), one could do

if NPY_UNLIKELY(len_args != nin) {
    if (len_args <= nop) {
       /* new deprecation stuff */
    }
    else {
       PyErrFormat(...);
       goto fail;
    } 
}

@lvllvl lvllvl force-pushed the issue-27639 branch 6 times, most recently from e4d6fa4 to 696f11a Compare May 30, 2025 01:32
@lvllvl lvllvl marked this pull request as ready for review May 31, 2025 19:03
@lvllvl lvllvl requested a review from seberg May 31, 2025 19:04
Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but this needs some work, also you didn't address the discussion around minimum and maximum.

"is deprecated; use out=keyword or np.maximum.reduce.");
PyErr_Format(PyExc_TypeError,
"np.maximum() takes exactly 2 input arguments (%zd given).",
len_args);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense to warn and error. You should just give the warning and test it. A warning can be turned into an error, in which case yuo need extra logic.

@lvllvl
Copy link
Contributor Author

lvllvl commented Aug 3, 2025

Thanks, but this needs some work, also you didn't address the discussion around minimum and maximum.

Hi @seberg,

I added some changes related to the discussion around maximum/minimum, e.g.,

  • when 2 or more positional args are pass to np.maximum, np.minimum there will be a DeprecationWarning
  • the warning will be about the positional out, the behavior will otherwise be unchanged

Let me know if that addresses all your concerns. I can add other changes as well. I don't know if changing documentation would be in this scope but I can add that as well, if needed.

@lvllvl lvllvl requested a review from seberg August 4, 2025 16:59
Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose @mhvk was also mildly in favor of just doing this (limited to minimum/maximum), so this looks pretty close, thanks.

a = np.array([1])
b = np.array([2])
out = np.empty_like(a)
with pytest.warns(DeprecationWarning, match=r"Passing more than 2 positional arguments to np\.maximum is deprecated"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is failing the linter for good reason.

@@ -4363,6 +4363,20 @@ ufunc_generic_fastcall(PyUFuncObject *ufunc,
Py_INCREF(tmp);
PyTuple_SET_ITEM(full_args.out, i-nin, tmp);
}

/* Extra positional args but *no* keywords */
if ((ufunc == UFUNC_MAXIMUM || ufunc == UFUNC_MINIMUM) && !all_none) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit tautological, you already have both ufunc == checks just below. TBH, I think it would be fine to just make one path though and don't care about the duplication. (or use ufunc->name.)

out = np.empty_like(a)

with pytest.warns(DeprecationWarning, match=r"Passing more than 2 positional arguments to np\.minimum is deprecated"):
result = np.minimum(a, b, out)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not flagging earlier, but please move this test (following the pattern there) to test_deprecations.py.

In general I don't care too much about it, but I do care about testing the error path when the warning is raised as an error, and that is not tested here, but the pattern in the file always tests it.

(And when you do that, you should see that the C code is missing error checks and the test fails in weird ways.)


/* Extra positional args but *no* keywords */
/* DEPRECATED NumPy 2.4, 2025-08 */
if (strcmp(ufunc->name, "maximum") == 0 || strcmp(ufunc->name, "minimum") == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I actually liked the old version of using the object, but maybe it doesn't matter on this path anyway.

Please don't use snprintf, DEPRECATE is a useful macro, but you don't need to use it, you can use PyWarn_Format (or whatever it was).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that the direct, very fast check with the object was better. We're not in a really hot path here, but still nice if something like np.add(in1, in2, out) remains as fast as possible.

Copy link
Contributor Author

@lvllvl lvllvl Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, my mistake I should've clarified before changing back to strcmp. I can change it back if you'd like.

I simplified the if-conditional like @mhvk suggested and I think I'll stick with the DEPRECATE macro, if you don't mind.

EDIT: I actually just re-inserted the faster version, instead of the strcmp version.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that this is mostly done - just small ideas for improvements.


/* Extra positional args but *no* keywords */
/* DEPRECATED NumPy 2.4, 2025-08 */
if (strcmp(ufunc->name, "maximum") == 0 || strcmp(ufunc->name, "minimum") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that the direct, very fast check with the object was better. We're not in a really hot path here, but still nice if something like np.add(in1, in2, out) remains as fast as possible.

@@ -480,3 +480,27 @@ def test_deprecated(self):
def test_not_deprecated(self, align):
# if the user passes a bool, it is accepted.
self.assert_not_deprecated(lambda: np.dtype("f8", align=align))

class TestTooManyArgsMaximum(_DeprecationTestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs an extra emptyline for PEP8 (below too).

Actually, if the message becomes the same anyway, you can combine the two with

class TestTooManyArgsExtremum(_DeprecationTestCase):
    ...
    @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)))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok thanks for the feedback. I made changes to the test and used Ruff to format the test.

if (strcmp(ufunc->name, "maximum") == 0 || strcmp(ufunc->name, "minimum") == 0) {
const char *which = ufunc->name; // "maximum" or "minimum"
char msg[256];
snprintf(msg, sizeof(msg),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the deprecation warning, I would suggest not worrying about adapting it to the name, but just keep it simple. Also, it should make clear why it is being deprecated. Maybe,

                "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."

@lvllvl lvllvl requested a review from seberg August 11, 2025 17:21
@lvllvl
Copy link
Contributor Author

lvllvl commented Aug 18, 2025

Hi @seberg , just wanted to double check - any other changes needed here or is this good to go?

Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it seems we did forget one thing: A deprecation should probably have a very brief release note.

Everything else looks fine though, just prefer less empty lines :).

@@ -65,6 +65,7 @@
#include "mapping.h"
#include "npy_static_data.h"
#include "multiarraymodule.h"
#include "../multiarray/number.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "../multiarray/number.h"
#include "number.h"

I think it should work and is what we mostly use.
I don't hate this, but don't want to break the pattern here (e.g. mapping.h is at the same spot).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok sounds good. committing your suggestion

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 "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"use the 'out' keyword argument instead. If you hoped to work with "
"use the `out=` keyword argument instead. If you hoped to work with "


@pytest.mark.parametrize("ufunc", [np.minimum, np.maximum])
def test_extremem_3_args(self, ufunc):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@lvllvl
Copy link
Contributor Author

lvllvl commented Aug 19, 2025

Sorry, it seems we did forget one thing: A deprecation should probably have a very brief release note.

Everything else looks fine though, just prefer less empty lines :).

ok adding an rst file with a short note on the deprecation.

@seberg
Copy link
Member

seberg commented Aug 20, 2025

Let's give this a shot, thanks @lvllvl.

@seberg seberg merged commit 5ba8c96 into numpy:main Aug 20, 2025
77 checks passed
@lvllvl lvllvl deleted the issue-27639 branch August 20, 2025 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ENH: show an warning when np.maximum received more than two inputs
4 participants