Skip to content
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

Merged
merged 3 commits into from Aug 8, 2017

Conversation

danielwe
Copy link
Contributor

@danielwe danielwe commented Aug 5, 2017

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__,

    def __ne__(self, other):
        return not self.__eq__(other)

should be avoided, since not NotImplemented evaluates to False, thus skipping the call to the reflected method on the other object in such cases. This pull request therefore replaces all such occurrences with

    def __ne__(self, other):
        return not self == other

and 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

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.
skirpichev added a commit to skirpichev/diofant that referenced this pull request Aug 5, 2017
skirpichev added a commit to skirpichev/diofant that referenced this pull request Aug 6, 2017
skirpichev added a commit to skirpichev/diofant that referenced this pull request Aug 6, 2017
skirpichev added a commit to skirpichev/diofant that referenced this pull request Aug 6, 2017
Also, use _sympifyit decorator in rich comparison methods.

Closes sympy/sympy#13078 (test from sympy/sympy#13091)
skirpichev added a commit to skirpichev/diofant that referenced this pull request Aug 6, 2017
@@ -329,7 +329,7 @@ def __ne__(self, other):

but faster
"""
return not self.__eq__(other)
return not self == other
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@skirpichev skirpichev Aug 6, 2017

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."

Copy link
Contributor Author

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.

Copy link
Member

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.

@danielwe
Copy link
Contributor Author

danielwe commented Aug 6, 2017

Failing checks are due to python 2 not raising TypeError when both sides of an inequality comparison return NotImplemented. Instead, the comparison evaluates to either True or False in a consistent (a < b implies b > a, not a > b, not b < a etc.), but seemingly arbitrary fashion. I'm currently looking into how to work around this.

@asmeurer
Copy link
Member

asmeurer commented Aug 6, 2017

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.
@asmeurer
Copy link
Member

asmeurer commented Aug 8, 2017

Looks good to me.

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.

Return NotImplemented, not False, upon rich comparison with unknown type
4 participants