-
-
Notifications
You must be signed in to change notification settings - Fork 11.2k
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
Conversation
There was a problem hiding this 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 aVisibleDeprecationWarning
. But I am a bit skeptical, also becauseDeprecationWarnings
are seen more these days anyway.
There was a problem hiding this 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;
}
}
e4d6fa4
to
696f11a
Compare
There was a problem hiding this 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.
numpy/_core/src/umath/ufunc_object.c
Outdated
"is deprecated; use out=keyword or np.maximum.reduce."); | ||
PyErr_Format(PyExc_TypeError, | ||
"np.maximum() takes exactly 2 input arguments (%zd given).", | ||
len_args); |
There was a problem hiding this comment.
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.
Hi @seberg, I added some changes related to the discussion around maximum/minimum, e.g.,
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. |
There was a problem hiding this 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.
numpy/_core/tests/test_umath.py
Outdated
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"): |
There was a problem hiding this comment.
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.
numpy/_core/src/umath/ufunc_object.c
Outdated
@@ -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) { |
There was a problem hiding this comment.
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
.)
numpy/_core/tests/test_umath.py
Outdated
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) |
There was a problem hiding this comment.
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.)
numpy/_core/src/umath/ufunc_object.c
Outdated
|
||
/* Extra positional args but *no* keywords */ | ||
/* DEPRECATED NumPy 2.4, 2025-08 */ | ||
if (strcmp(ufunc->name, "maximum") == 0 || strcmp(ufunc->name, "minimum") == 0) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
numpy/_core/src/umath/ufunc_object.c
Outdated
|
||
/* Extra positional args but *no* keywords */ | ||
/* DEPRECATED NumPy 2.4, 2025-08 */ | ||
if (strcmp(ufunc->name, "maximum") == 0 || strcmp(ufunc->name, "minimum") == 0) { |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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)))
There was a problem hiding this comment.
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.
numpy/_core/src/umath/ufunc_object.c
Outdated
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), |
There was a problem hiding this comment.
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."
Hi @seberg , just wanted to double check - any other changes needed here or is this good to go? |
There was a problem hiding this 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 :).
numpy/_core/src/umath/ufunc_object.c
Outdated
@@ -65,6 +65,7 @@ | |||
#include "mapping.h" | |||
#include "npy_static_data.h" | |||
#include "multiarraymodule.h" | |||
#include "../multiarray/number.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#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).
There was a problem hiding this comment.
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
numpy/_core/src/umath/ufunc_object.c
Outdated
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 " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"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): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok adding an |
Let's give this a shot, thanks @lvllvl. |
attempts to resolve #27639