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

__eq__ and __ne__ are implemented incorrectly for all classes #3455

Closed
remcohaszing opened this issue May 29, 2017 · 7 comments
Closed

__eq__ and __ne__ are implemented incorrectly for all classes #3455

remcohaszing opened this issue May 29, 2017 · 7 comments
Assignees
Labels
api: core type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.

Comments

@remcohaszing
Copy link

remcohaszing commented May 29, 2017

__eq__ and __ne__ are implemented incorrectly throughout the entire google-cloud-* code base. I encountered this issue when trying to compare a google.cloud.datastore.Key instance to unittest.mock.ANY.

When implementing custom equality in Python (2 or 3), it should be written like this:

class A:
    def __eq__(self, other):
        if not isinstance(other, A):
            # Delegate comparison to the other instance's __eq__.
            return NotImplemented
        # Whatever logic applies to equality of instances of A can be added here.
        ...

    def __ne__(self, other):
        # By using the == operator, the returned NotImplemented is handled correctly.
        return not self == other

Throughout the entire code base of this repository, equality is implemented like this:

class A:
    def __eq__(self, other):
        if not isinstance(other, A):
            # Other instances are never equal.
            return False
        # Whatever logic applies to equality of instances of A can be added here.
        ...

    def __ne__(self, other):
        # By negating the return value, NotImplemented is treated as False.
        return not self.__eq__(other)

As a result gcloud instances can never equal entities of other classes if they are the first object in the comparison. This is not an issue for most cases, but there are cases where this is an issue. Also this behaviour is simply incorrect.

The __ne__ implementation works, because __eq__ is implemented incorrectly. The boolean value of NotImplemented is True.

>>> not NotImplemented
False

This is typically an issue when unittesting.

>>> from unittest.mock import ANY
>>> 
>>> from google.cloud.datastore import Client
>>> 
>>> 
>>> client = Client()
>>> key = client.key('foo')
>>> key == ANY  # Expected True
False
>>> ANY == key
True
>>> key != ANY  # Expected False
True
>>> ANY != key
False
>>> 
@dhermes dhermes added api: core type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns. labels May 31, 2017
@dhermes
Copy link
Contributor

dhermes commented May 31, 2017

Thanks @remcohaszing for taking the time to report this! As of 5e86158 it seems to impact 5 subpackages:

$ git grep -l 'def __eq__' | cut -d '/' -f 1 | uniq
bigquery
bigtable
datastore
monitoring
spanner

Unfortunately, it's spread out in an ad-hoc way so fixing everything will take awhile:

$ git grep 'def __eq__' | wc
     36     144    2602
$ git grep 'def __ne__' | wc
     16      64    1159

@lukesneeringer
Copy link
Contributor

@remcohaszing Sanity check: Should the if isinstance(other, A): actually be if not isinstance(other, A):?

@remcohaszing
Copy link
Author

@lukesneeringer You're right, my mistake.

@lukesneeringer
Copy link
Contributor

lukesneeringer commented Aug 8, 2017

@dhermes Should I hold off on this then? I was going to knock them all out.

@dhermes
Copy link
Contributor

dhermes commented Aug 8, 2017

No, go for it. I was just giving you an example of the "correct" thing.

Any time I've "done the right thing" was for new code.

@lukesneeringer
Copy link
Contributor

Okay, that was what I thought, but I wanted to be polite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: core type: bug Error or flaw in code with unintended results or allowing sub-optimal usage patterns.
Projects
None yet
Development

No branches or pull requests

3 participants