Skip to content

BUG: core: ensure __r*__ has precedence over __numpy_ufunc__ #3856

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

Merged
merged 1 commit into from
Oct 19, 2013

Conversation

pv
Copy link
Member

@pv pv commented Oct 2, 2013

Add a special case to the implementation of ndarray.__mul__ et al. that refuses to work on other objects that are not ndarray subclasses and implement both __numpy_ufunc__ and __r*__.

This way, execution passes first to the custom __r*__ method, which makes it possible to have e.g. __mul__ and np.multiply do different things.

Additionally, disable one __array_priority__ special case handling when also __numpy_ufunc__ is defined (which may have been the reason why __rmul__ continued to work in scipy.sparse matrices).

Addresses gh-3812

Feedback welcome --- I think this solves the issue without being too magical, but YMMV

@njsmith
Copy link
Member

njsmith commented Oct 2, 2013

Yeah, the documented rule is that all the __special__ methods defer to __rspecial__ if the other side wins on __array_priority__.

More generally it's not really clear to me what the motivation for this PR is? The way things work right now, we already have a well-defined mechanism for adjudicating between __special__ and __rspecial__ for ndarrays, and if this method decides that ndarray.__special__ should win, then that calls the ufunc logic, which does whatever it does (perhaps calling __numpy_ufunc__). I'm not a big fan of __array_priority__ in general, but it does work, and seems to accomplish everything this code does, without adding new special cases like making non-ufunc code know about __numpy_ufunc__. What does this code improve?

@pv
Copy link
Member Author

pv commented Oct 2, 2013

The point is to replace __array_priority__ with a simpler approach that does not require assigning arbitrary floating point numbers to specify the order in which the methods are called.

I'd believe that "__rmul__ in favor of __numpy_ufunc__" is simpler than: "__rmul__ in favor of __numpy_ufunc__, except if array_priority happens to be smaller than that of the other class and your class is not a subclass of ndarray."

The point of array_priority was previously only in ufunc output and input type determination, with some right-hand special cases hacked in. This part of the mechanism is completely disabled by the more Python-like approach now in numpy_ufunc. As far as ndarray subclasses are concerned, the changes in this PR are not needed, as Python works out the call order correctly itself. (EDIT: Except for the in-place ops --- I'm less sure whether what I did to them here is the correct thing. EDIT2: also, operations concerning different ndarray subclasses in which the other is not a subclass of the other probably also don't work correctly without these changes; I doubt this case worked out correctly with array_priority)

The remaining issue is in non-ndarray subclasses. A similar binop special case as to what I add in this PR was added a couple of months ago in 4441bdd for array_priority --- also that is not in the ufunc code. (EDIT2: similar issue occurs AFAICS for ndarray subclasses in which one is not a subclass of the other --- I think the changes in that commit don't fix the behavior for this case)

However, that change did not yet completely fix __array_priority__:

  • It is not handled correctly in comparison ops
  • It is not handled in in-place operations
  • __array_priority__ disables __numpy_ufunc__ completely for all right-hand operations, even if there are no corresponding __r*__ methods
  • EDIT3 __array_priority__ is not respected vs. __numpy_ufunc__ for Python binops in ndarray subclasses

EDIT3 This PR fixes the above four issues. It might be possible to fix them also by reworking the __array_priority__ binop handling, but that would also require moving parts of the code out from ufuncs to the Python binop side. I wouldn't like to do this, since the normal Python NotImplemented returns can, as far as I see, handle the issue here without introducing a call order based on floating point values.

@charris
Copy link
Member

charris commented Oct 16, 2013

@pv Needs rebase.

@pv
Copy link
Member Author

pv commented Oct 19, 2013

Rebased.

return 0;
}
if (!inplace_op && PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))
|| !PyArray_Check(self)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|| on previous line.

@charris
Copy link
Member

charris commented Oct 19, 2013

LGTM modulo a nitpick.

Add a special case to the implementation of ndarray.__mul__ et al. that
refuses to work on other objects that are not ndarray subclasses and
implement both __numpy_ufunc__ and __r*__.

This way, execution passes first to the custom __r*__ method, which
makes it possible to have e.g. __mul__ and np.multiply do different
things.

Additionally, disable one __array_priority__ special case handling when
also __numpy_ufunc__ is defined.
@pv
Copy link
Member Author

pv commented Oct 19, 2013

Fix'd. What about @njsmith's objection that this is duplicating a similar thing as array_priority?

My view on the situation is that __numpy_ufunc__ obsoletes the which-subclass-wins dance as far as binary ops are concerned; array priority is not used inside the ufunc when __numpy_ufunc__ is present, and I think it is simpler if it is not used outside either in this case.

@charris
Copy link
Member

charris commented Oct 19, 2013

@pv That has been my impression, although I haven't looked deeply into it. I think array priority works for some things, it did OK for the Polynomial classes, but many want to have more functionality than that and end up subclassing ndarray, mistakenly IMHO. Ndarray was not really designed as a virtual base class. So I think this helps with that. If it looks a bit of a kludge in some parts, I think that is unavoidable.

The upshot is, I'm trusting your judgement on this one ;)

charris added a commit that referenced this pull request Oct 19, 2013
BUG: core: ensure __r*__ has precedence over __numpy_ufunc__
@charris charris merged commit 55d3ef5 into numpy:master Oct 19, 2013
@charris
Copy link
Member

charris commented Oct 19, 2013

Too bad this doesn't help with #3908 ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants