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
Improve rich comparison methods, including fix for #13078 #13091
Conversation
Return NotImplemented when a rich comparison is unable to make sense of the input types. This way, the python interpreter is able to delegate to reflected methods as appropriate in order to maintain symmetric relations. This commit also removes direct invocations of rich comparison methods, like __eq__, except in super calls, explicitly reflected calls, and other cases where the order is essential (see, e.g., sympy#7951). These calls may sidestep the desired dispatching and introduce bugs by evaluating, e.g., not NotImplemented to False. Closes sympy#13078.
Was there originally a reason for this apparently obvious bug? I understand that comparison with irrational NumberSymbols is finicky, as most recently discussed in sympy#13081, but if this was a "workaround" hack, it only seems to make things even more inconsistent. Here's to maintaining symmetric relations.
Taken mostly from sympy/sympy#13091.
Also, use _sympifyit decorator in rich comparison methods. Closes sympy/sympy#13078 (test from sympy/sympy#13091)
Taken mostly from sympy/sympy#13091.
@@ -329,7 +329,7 @@ def __ne__(self, other): | |||
|
|||
but faster | |||
""" | |||
return not self.__eq__(other) | |||
return not self == other |
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 this the correct result when self == other
returns NotImplemented
?
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 point is that self == other
never returns NotImplemented
. If self.__eq__(other)
returns NotImplemented
, the comparison is delegated to other.__eq__(self)
. If it, too, returns NotImplemented
, self == other
evaluates to False
.
This way, a != b
always returns the opposite of a == b
. If using the not self.__eq__(other)
pattern, this short circuits the method resolution such that both a != b
and a == b
may return False
in some circumstances.
For other rich comparison operators (<, >
etc.), the method resolution is similar, and if both sides return NotImplemented
a TypeError
is raised.
The pull request includes tests for all of these details.
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.
btw, this method redundant, isn't?
from py3 docs:
"By default, __ne__()
delegates to __eq__()
and inverts the result unless it is NotImplemented."
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.
Redundant in python 3, but not in python 2.
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 point is that self == other never returns NotImplemented.
Ah... I see. That explains the change.
Failing checks are due to python 2 not raising |
We may need to leave the inequalities alone. We strictly speaking break against the python 2 behavior of allowing arbitrary comparisons for the more sane python 3 behavior of only allowing mathematically consistent comparisons. |
Python 2 is all gung-ho about inequalities, so sympy must take responsibility. The sane behavior (raising TypeError if both operands' methods return NotImplemented) was not implemented until python 3.
Looks good to me. |
Comparison methods are modified to return
NotImplemented
when comparing against an unknown type that cannot be sympified. The rationale is described in #13078, and details of how python resolves binary operators to methods are described at https://docs.python.org/3/reference/datamodel.html and http://docs.sympy.org/latest/python-comparisons.html.All comparisons between sympy/sympifiable types are left unchanged.
The common pattern for defining
__ne__
,should be avoided, since
not NotImplemented
evaluates toFalse
, thus skipping the call to the reflected method on the other object in such cases. This pull request therefore replaces all such occurrences withand this pattern is extended to most other cases where a rich comparison method used to be called directly (unless it's a super call, an explicit call to the reflected method on the other object, or the special cases in #7951).
The final commit fixes what appears to be a bug in the way reflected methods are called in comparisons involving
NumberSymbol
instances. While these comparisons are tricky (see #13081), it seems strange for them not to be symmetric wrt. their reflections.Fixes #13078