-
-
Notifications
You must be signed in to change notification settings - Fork 12k
BUG: Pass non-None outputs to __array_prepare__ and __array_wrap__
#10919
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
BUG: Pass non-None outputs to __array_prepare__ and __array_wrap__
#10919
Conversation
e4b92a9 to
71e9258
Compare
|
Overall, I think this does the right thing and it looks good. My main question is whether it is really all that helpful to introduce the new struct with Whether or not the new code is worth may depend mostly on where you see it going: as you note, there is substantial refactoring still to be done, and if you think the structure will make that much easier, then perhaps it is worth keeping it. |
|
This structure makes it more suitable for use with |
numpy/core/src/umath/ufunc_object.c
Outdated
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.
@mhvk: The separation between in and out makes this a little more transparent (no idx_offset or start_idx)
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.
True, but at the cost of a new function that combines in and out to remake arr_prep_args; just correcting the implementation of that would have a much smaller footprint.
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.
That combination had to be added somewhere - part of the original bug was that only *args was being used, rather than a normalized args struct. Either we combine them at the call site, or we do it during the parse step.
Since we treat in and out differently in a bunch of places, it seems most useful to keep them separate till the last minute.
numpy/core/tests/test_umath.py
Outdated
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.
Perhaps I should leave this test as it was - this was inherited from the earlier implementations
8a0866f to
765fd45
Compare
|
Alright, squashed |
765fd45 to
5d8b135
Compare
|
And now with much clearer tests, a comparison to the old behavior, and a couple new test cases |
mhvk
left a comment
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.
Looks good to me! Only one suggestion remaining, but at it is purely a suggested short-cut, I'll approve now.
numpy/core/src/umath/ufunc_object.c
Outdated
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.
Couldn't one just do
full_args->in = PyTuple_Slice(args, 0, nin);
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.
Yeah, I see no reason why that wouldn't work
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.
Ah, I remember why - PyTuple_Slice does a tonne of extra checks we don't need:
Given that ufunc overhead seems to be something that comes up a lot, I though that this was worth optimizing.
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 think the overhead is negligible compared to other things (like just looking up a method...) -- after all, it is just a few checks of ints -- and one has to weigh the overhead against the advantage of clarify, but I do not really mind either way. If you don't change it, maybe add a comment? (saying that we do what PyTuple_Slice would do, but without the overhead of its checks?)
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.
Yes, adding a comment seems reasonable
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 take it back, PyTuple_GetSlice removes the need for my optimization above too - so I'll switch
5d8b135 to
665087f
Compare
|
Updated with |
|
OK, the slice indeed also had the logic for the full slice in place, so now makes it quite a bit shorter, nice. Sadly, it needs a rebase now... |
…repare__ or __array_wrap__ arguments This fixes numpygh-10450. The effect here is that all of these will pass `args=(in1, in2)` to `__array_prepare__` and `__array_wrap__`: * `ufunc(in1, in2)` - unchanged * `ufunc(in1, in2, None)` - previously: * prepare: `(in1, in2, None)` * wrap: `(in1, in2, None)` * `ufunc(in1, in2, out=None)` - previously: * prepare: `(in1, in2, None)` * `ufunc(in1, in2, out=(None,))` - previously * prepare: `(in1, in2, (None,))` Whereas these will pass `args=(in1, in2, out)`; * `ufunc(in1, in2, out)` - unchanged * `ufunc(in1, in2, out=out)` - previously * wrap: `(in1, in2)` * `ufunc(in1, in2, out=(out,))` - previously * prepare: not called on out at all! * wrap: `(in1, in2)`
665087f to
40868df
Compare
|
Rebased. |
| make_full_arg_tuple( | ||
| ufunc_full_args *full_args, | ||
| npy_intp nin, npy_intp nout, | ||
| PyObject *args, PyObject *kwds) |
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.
shouldn't the output arguments be last in the declaration?
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.
Putting them first matches memcpy(dst, src, n) and f(dst, args...) more closely resembles dst = f(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.
I slightly wondered why the routine doesn't just return full_args (and NULL if it fails).
(Beyond that, @mattip, are you OK with the current implementation? My sense is that it is ready to go in.)
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 is an improvement, from here refactoring will be easier
| Py_INCREF(args); | ||
| return args; | ||
| /* This should have been checked by the caller */ | ||
| assert(nin <= nargs && nargs <= nin + nout); |
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.
assert will be ignored in release
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.
That's fine, I don't think this can ever happen
| out_kwd = kwds ? PyDict_GetItem(kwds, npy_um_str_out) : NULL; | ||
|
|
||
| if (out_kwd != NULL) { | ||
| assert(nargs == nin); |
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.
Is there a check elsewhere? assert will be ignored in release
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.
Yep, this check already happens in the first round of argument parsing
|
@mhvk: Anything else needed here? |
|
I'm hoping this might make fixing #9022 easier |
|
OK, let's get it in! |
Small thing missed in numpygh-10919.
__array_prepare__ and __array_wrap__
Fixes gh-10450.
This is the alternative to #10470 and #10488.
I've made these changes on top of one another, so the commit diffs are largely meaningless (will squash before merging)
The effect here is that all of these will pass
args=(in1, in2)to__array_prepare__and__array_wrap__:ufunc(in1, in2)- unchangedufunc(in1, in2, None)- previously:(in1, in2, None)(in1, in2, None)ufunc(in1, in2, out=None)- previously:(in1, in2, None)ufunc(in1, in2, out=(None,))- previously(in1, in2, (None,))Whereas these will pass
args=(in1, in2, out);ufunc(in1, in2, out)- unchangedufunc(in1, in2, out=out)- previously(in1, in2)ufunc(in1, in2, out=(out,))- previously(in1, in2)