-
-
Notifications
You must be signed in to change notification settings - Fork 11.1k
BUG: don't silence __array_wrap__
errors in ufunc.reduce
#9022
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
8affdad
to
fbb53d0
Compare
@@ -53,7 +53,8 @@ def __new__(cls, data, metadata=None): | |||
return res | |||
|
|||
def __array_wrap__(self, obj, context=None): | |||
obj.metadata = self.metadata | |||
if isinstance(obj, MyArray): | |||
obj.metadata = self.metadata |
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 was previously crashing, because obj
was ndarray
, and did not have the metadata property.
I think there's a bigger issue here in that __array_prepare__
is not called either, but with __array_ufunc__
approaching release, I don't think we need to fix that any more
numpy/core/src/umath/ufunc_object.c
Outdated
} | ||
else { | ||
else if (wrap != NULL) { | ||
PyObject *res = PyObject_CallFunctionObjArgs( |
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.
Would almost prefer putting the initialization fo res
on another line just to have space to put it all on a single line.
numpy/core/src/umath/ufunc_object.c
Outdated
else if (wrap != NULL) { | ||
PyObject *res = PyObject_CallFunctionObjArgs( | ||
wrap, ret, Py_None, NULL); | ||
/* Handle __array_wrap__ that does not accept a context argument */ |
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.
Blank lines after declarations is preferred.
numpy/core/src/umath/ufunc_object.c
Outdated
if (res == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) { | ||
PyErr_Clear(); | ||
res = PyObject_CallFunctionObjArgs( | ||
wrap, ret, NULL); |
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 all should fit on one line I think.
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.
Again, copied from the other location. I guess I'll fix both
numpy/core/src/umath/ufunc_object.c
Outdated
else { | ||
PyObject *wrap; | ||
_find_array_wrap(args, kwds, &wrap, 1, 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.
I'd move this blank line up to after 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.
Extra blank line above else
. I'm curious if the wrap
here matters, the return will already have happened if out
is not NULL.
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.
NVM, I see that this is to wrap the return when out is missing.
numpy/core/src/umath/ufunc_object.c
Outdated
@@ -68,6 +68,9 @@ | |||
/********************/ | |||
|
|||
/* ---------------------------------------------------------------- */ | |||
static void | |||
_find_array_wrap(PyObject *args, PyObject *kwds, | |||
PyObject **output_wrap, int nin, int 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.
Needs one more space of indentation.
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.
Just copied from the definition, I think - I'll fix it there too
Couple of style nits. Reading the ufunc code makes my head ache... |
Be good to finish this up. |
I'll take a look in the next few days once my schedule clears |
fbb53d0
to
2122172
Compare
numpy/core/src/umath/ufunc_object.c
Outdated
PyErr_Clear(); | ||
else { | ||
PyObject *wrap; | ||
_find_array_wrap(args, kwds, &wrap, 1, 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.
Turns out this doesn't work, as it interprets the axis
argument as an out
argument
2122172
to
8789582
Compare
Overhauled, with the first two trivial cleanup commits extracted to #10988 |
8789582
to
1d6d047
Compare
I'll see if I can revive this this weekend - seems my latest fixups segfault... |
1d6d047
to
a9b063f
Compare
Hopefully fixed by #11296 |
numpy/core/src/umath/ufunc_object.c
Outdated
return (PyObject *)ret; | ||
} | ||
/* Wrap and return the output */ | ||
{ |
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 still don't like free floating blocks :)
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.
Would you prefer if(true) {
? :)
I could extract this to another _wrap_reduction_output
helper function, but I think that starts to obscure the goal here.
Usually my goal with these floating blocks is to limit the scope of the variables, to make it clear where they should be used. C99 will remove most of the reason for my use of them
numpy/core/src/umath/ufunc_object.c
Outdated
else if (res == Py_None) { | ||
Py_DECREF(res); | ||
if (out != NULL) { | ||
full_args.out = PyTuple_Pack(1, 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.
Always one output in reductions?
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, reductions only apply to binary input unary output ufuncs
numpy/core/src/umath/ufunc_object.c
Outdated
return res; | ||
} | ||
fail: | ||
Py_DECREF(obj); |
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.
Don't need to Py_DECREF(wrap)
? I see it is done in the one place above, just want to make sure everything that jumps to here has taken care of it.
py_context = Py_BuildValue("OOi", | ||
context->ufunc, args_tup, context->out_i); | ||
Py_DECREF(args_tup); | ||
if (py_context == NULL) { |
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.
What about wrap
?
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.
Good catch, thanks
PyObject *args_tup; | ||
/* Call the method with appropriate context */ | ||
args_tup = _get_wrap_prepare_args(context->args); | ||
if (args_tup == NULL) { |
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.
What about wrap
?
7546bf7
to
f25853a
Compare
Ok, fixed the missing decref on Unfortunately |
I'm sorry I'm late too this, but I'm worried about the change in behaviour for reductions. Is it needed to wrap even output that is provided or output that has the same type as the input? Since we don't pass in a context here, an implementation of |
numpy/core/src/umath/ufunc_object.c
Outdated
@@ -87,6 +94,15 @@ _get_wrap_prepare_args(ufunc_full_args full_args) { | |||
} | |||
} | |||
|
|||
static void |
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.
Maybe move the functions up instead?
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'd rather keep the diff smaller to make the blame easier
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.
Fine to make it another commit; I find these prototypes before the actual code very confusing...
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.
Done - I suppose in a standalone commit, a messy diff is fine if it comes with a commit message assuring nothing really changed.
f25853a
to
ae51067
Compare
You're right, probably safer not to change the behavior here. I've modified it to keep the |
PyErr_Clear(); | ||
} | ||
else if (res == Py_None) { | ||
Py_DECREF(res); |
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 this line was intended to mean a.__array_wrap__ is None
, not a.__array_wrap__() is None
facd479
to
90d747a
Compare
…ce as in ufunc.__call__ Previously they were silenced
…es are not needed The functions themselves have not changed, only moved
90d747a
to
e3b0a3f
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.
Looks nice to me now; only one nit-pick argument which you should feel free to ignore (maybe it is even wrong).
numpy/core/src/umath/ufunc_object.c
Outdated
else { | ||
PyObject *res; | ||
PyObject *args_tup; | ||
_ufunc_context context; |
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.
Maybe just _ufunc_context context = {ufunc, full_args, i}
?
else if (res == Py_None) { | ||
Py_DECREF(res); | ||
/* Wrap and return the output */ | ||
{ |
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 must admit I find this open context ugly too - I'm trained to think I must be within a special block. I'd just move the PyObject *wrap
to the top...
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 put the comment before the block to make it clearer that there was no control structure missing. I think that the reduced number of local variable to keep track of is worth having a briefly misleading {
. Also, when we adopt C99, cases like this can all be removed with a find and replace.
else { | ||
PyObject *res; | ||
PyObject *args_tup; | ||
context.ufunc = 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.
Now I think I've made the comment, but perhaps forgot to submit: maybe just define _ufunc_context context = {ufunc, full_args, i};
? Or is that incorrect syntax? (My C remains quite hopeless...)
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.
Sure, that seems reasonable - Definitely correct in some version of C, I think C90 will cope.
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.
Two nitpicks left (one shared by @charris...)
numpy/core/src/umath/ufunc_object.c
Outdated
@@ -4555,42 +4631,15 @@ ufunc_generic_call(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds) | |||
/* wrap outputs */ | |||
for (i = 0; i < ufunc->nout; i++) { | |||
int j = ufunc->nin+i; | |||
PyObject *wrap = wraparr[i]; | |||
_ufunc_context context = {ufunc, full_args, i}; |
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.
Appveyor complains about this line (https://ci.appveyor.com/project/charris/numpy/build/1.0.10809/job/3m1asbd6b1v588ro#L1894):
numpy\core\src\umath\ufunc_object.c(4634) : error C2440: 'initializing' : cannot convert from 'ufunc_full_args' to 'PyObject *'
Does look like the definition is indeed off.
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 like a bug in the old msvc needed by python 2.7 and 3.4 - guess I'll revert
0751591
to
e3b0a3f
Compare
@mhvk is this ready to go? |
Yes, I approved this quite a while ago, and the nitpicks were discussed. Thanks for checking, @mattip! |
__array_wrap__
errors in ufunc.reduce
Raised by #5819 (comment).
__array_wrap__(self, obj, context)
, with no default argument forcontext
, would find that this method was never called byreduce
__array_wrap__
were silenced, and the result downcast to ndarray