ENH: show warning when np.maximum receives more than 2 inputs#29052
ENH: show warning when np.maximum receives more than 2 inputs#29052seberg merged 10 commits intonumpy:mainfrom
Conversation
seberg
left a comment
There was a problem hiding this comment.
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 becauseDeprecationWarningsare seen more these days anyway.
mhvk
left a comment
There was a problem hiding this comment.
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
seberg
left a comment
There was a problem hiding this comment.
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.
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. |
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.
This is failing the linter for good reason.
numpy/_core/src/umath/ufunc_object.c
Outdated
| } | ||
|
|
||
| /* Extra positional args but *no* keywords */ | ||
| if ((ufunc == UFUNC_MAXIMUM || ufunc == UFUNC_MINIMUM) && !all_none) { |
There was a problem hiding this comment.
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.
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.
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.
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.
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.
mhvk
left a comment
There was a problem hiding this comment.
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.
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.
| # 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.
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.
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.
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? |
seberg
left a comment
There was a problem hiding this comment.
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
| #include "mapping.h" | ||
| #include "npy_static_data.h" | ||
| #include "multiarraymodule.h" | ||
| #include "../multiarray/number.h" |
There was a problem hiding this comment.
| #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.
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.
| "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): | ||
|
|
ok adding an |
|
Let's give this a shot, thanks @lvllvl. |
…29052) Deprecate not using `out=` for np.minimum and maximum due to it being a common confusion.
…29052) Deprecate not using `out=` for np.minimum and maximum due to it being a common confusion.
…29052) Deprecate not using `out=` for np.minimum and maximum due to it being a common confusion.
attempts to resolve #27639