-
-
Notifications
You must be signed in to change notification settings - Fork 11.1k
MAINT: move comparison operator special-handling out of ufunc parsing. #11282
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
* is counted as inflexble. (This repeats work done in the ufunc, | ||
* but OK to waste some time in an unlikely path.) | ||
*/ | ||
if (PyArray_GetArrayParamsFromObject( |
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 tried using the seemingly more logical PyArray_FromAny(other, NULL, 0, 0, 0, NULL)
but somehow this returns a failure when other is a python string.
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.
You probably need to PyErr_Fetch
above, before you start calling any more functions - most python functions are not willing to run if an exception is actively being thrown
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, yes, that indeed does it!
This presumably means |
@eric-wieser - yes, indeed, the ufuncs themselves will no longer return |
Can you add some tests for ufuncs no longer returning NotImplemented? |
* silenced error. | ||
*/ | ||
NPY_NO_EXPORT int | ||
DEPRECATE_FUTUREWARNING_silence_error(const char *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.
Maybe better to just add a PyObject *warning_type
argument to the original function, and use PyErr_WarnEx(warning_type, msg, 1)
in place of DEPRECATE_FUTUREWARNING
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.
Since I needed the fetch the error anyway, the need for the new function disappeared...
int other_type_num, other_is_flexible; | ||
/* | ||
* Determine whether other has a flexible dtype; here, inconvertible | ||
* is counted as inflexble. (This repeats work done in the 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.
typo
return Py_NotImplemented; | ||
} | ||
else if (other_is_flexible) { | ||
/* |
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.
weird indent
It would be nice even at this early stage to document the user-visible changes and change the tests appropriately |
c49d901
to
c8dffb2
Compare
OK, now simpler with @mattip - agreed about documentation, but there isn't even a file for the 1.16 release notes yet... But the main effect is the one @eric-wieser noted: now the |
Just to emphasize, do not merge before the 1.15 branch. |
c8dffb2
to
61bd834
Compare
OK, tests now all pass - further review much appreciated! |
61bd834
to
72bab23
Compare
Rebased, so now this is just a single commit. |
72bab23
to
f7029ce
Compare
With 1.15 branches, I rebased this and added a release note for 1.16. If everyone is still happy with this, it would be nice to merge it, since I have other changes to the ufunc parsing in the pipeline (#11333 in particular). |
@mattip, @eric-wieser - any further comments on this? |
Py_DECREF(array_other); | ||
} | ||
else { | ||
PyErr_Clear(); /* we restore the original error 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.
Would be clearer if other_is_flexible
and ndim_other
were assigned in this path, rather than initialized.
The argument parsing for ufuncs contains special logic to enable returning NotImplemented for comparison functions. The ufuncs, however, should not have to know whether they were called via a python comparison operator, so this PR moves the logic to the operators, checking after ufunc failed whether a NotImplemented should be returned.
f7029ce
to
13c42ef
Compare
OK, thanks, did all three. |
/* | ||
* In future, we should create a correctly shaped | ||
* array of bool. For now, a placeholder error. | ||
*/ |
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 should
- Appear before the
DEPRECATE_FUTUREWARNING
line - say
placeholder warning
, not placeholder error.
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 just that if the deprecation is removed, the chained exception should be replaced (I wanted to have code that would at least work, but didn't feel like creating the array now...). I clarified this.
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 say that's obvious, in the same way that the return NULL
should also be replaced
return Py_NotImplemented; | ||
} | ||
if (result == 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.
Can you extract the contents of this long if
to _failed_compare_workaround(self, other, op)
, or something similar (documented to expect an exception to already be in flight)
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.
OK, pushed an extra commit moving the failure path to its own function. |
* ordering, so we emit a warning. | ||
*/ | ||
#if !defined(NPY_PY3K) | ||
if (DEPRECATE( |
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.
Date and version comment needed
* If other did not have a flexible dtype, the error cannot | ||
* have been caused by a lack of implementation in the ufunc. | ||
*/ | ||
if (DEPRECATE( |
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.
Date and version comment needed
* array of bool. | ||
*/ | ||
if (ndim_other != 0 || PyArray_NDIM(self) != 0) { | ||
if (DEPRECATE_FUTUREWARNING( |
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.
Date and version comment needed
"elementwise comparison failed; " | ||
"this will raise an error in the future.") < 0) { | ||
PyArray_ChainExceptionsCause(exc, val, tb); | ||
return 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.
I think it would be tidy to move this to a fail:
label
} | ||
else { | ||
/* LE, LT, GT, or GE with non-flexible other; just pass on error */ | ||
PyErr_Restore(exc, val, tb); |
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 could also be the fail label - PyArray_ChainExceptionsCause
is a shorhand for PyErr_Restore
if no exception is set.
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, this makes a lot more sense.
8574044
to
a2b9baa
Compare
@mattip, @eric-wieser - all implemented. |
|
||
/* LE, LT, GT, or GE with non-flexible other; just pass on error */ | ||
fail: | ||
PyErr_Restore(exc, val, tb); |
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 line should be the ChainExceptions
, not PyErr_Restore
. The former implies the latter, not vice versa
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.
A comment here saying "Reraise the original exception, or chain the new one" might be nice too
doc/release/1.16.0-notes.rst
Outdated
Comparison ufuncs will now error rather than return NotImplemented | ||
------------------------------------------------------------------ | ||
|
||
Previously, comparison ufuncs would return `NotImplemented` if their arguments |
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.
"such as np.equal
" might be helpful here.
doc/release/1.16.0-notes.rst
Outdated
------------------------------------------------------------------ | ||
|
||
Previously, comparison ufuncs would return `NotImplemented` if their arguments | ||
had structured dtypes, to help the comparison operators deal with those. This |
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.
Can you point out that the comparison operators continue to return NotImplemented
as before?
* For scalars, returning NotImplemented is correct. | ||
* For arrays, we emit a future deprecation warning. | ||
* If that is removed, the failure jump should be replaced | ||
* by the creation of a correctly shaped array of bool. |
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 just "when this warning is removed, it should return a correctly-shaped array of bool.
return Py_NotImplemented; | ||
} | ||
|
||
/* LE, LT, GT, or GE with non-flexible other; just pass on error */ |
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 probably add an else { goto fail; }
here, just to have somewhere obvious to tie the comment to. The compiler should optimize it just the same anyway
a2b9baa
to
533bb37
Compare
@eric-wieser - thanks for the further comments; all done. |
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.
Implementation looks good now.
Did you just cut up the original long comment, or did you throw it out and reword it? I haven't yet checked to see if all of the important bits survived.
Yes, I reworded the long comment, removing parts not relevant for comparison operators. I think it now (even) more clearly states where we want to go. |
As this was approved, I'll go ahead and merge so I can can rebase some of the other PRs (which will be easier to look at without the long distracting stuff in |
} | ||
else { | ||
/* LE, LT, GT, or GE with non-flexible other; just pass on error */ | ||
goto fail; |
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'm not clear on why this path doesn't return NotImplemented in py3
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.
The reason is that the comparison is actually implemented, i.e., the error is real and we act just like we would do for __add__
(note that the behaviour was the same before this PR).
The argument parsing for ufuncs contains special logic to enable returning
NotImplemented
for comparison functions. The ufuncs, however, should not have to know whether they were called via a python comparison operator, so this PR moves the logic to the operators, checking after ufunc failedwhether a
NotImplemented
should be returned.Follow-up of #11260, in particular #11260 (comment)