Search code examples
pythonequality

Equality Operator Producing True For 2 different Objects in Memory


Why does my equality method produce True when the 2 objects point and b point to 2 different objects in memory?

import math


def main():

    point = Point(2, 3)

    print(point == Point(2, 3))

    b = Point(2, 3)

    print(id(point), id(b))


class Point:

    def __init__(self, x=0, y=0):
         self.x = x
         self.y = y

    def distance_from_origin(self):
         return math.hypot(self.x, self.y)

    def __eq__(self, other):
         return id(self.x) == id(other.x) and id(self.y) == id(other.y)

    def __repr__(self):
         return f"Point({self.x!r}, {self.y!r})"

    def __str__(self):
         return f"{self.x!r}, {self.y!r}"

if name == 'main': main()


Solution

  • id of Point objects are different, because they're different objects and there is no cache/interning mechanism for them (which would be wrong because they're mutable).

    == works because when invoking == on Point, you call __eq__ and it's coded like this:

    def __eq__(self, other):
         return id(self.x) == id(other.x) and id(self.y) == id(other.y)
    

    so, it's wrong, but it works most of the time because of interning of integers from -5 to 256 in CPython (further tests show that it works with bigger values but it's not guaranteed). Counter example:

    a = 912
    b = 2345
    
    point = Point(a, b)
    
    print(point == Point(456*2, b))
    

    you'll get False even if 456*2 == 912

    Rewrite as this so you won't have surprises with big integers:

    def __eq__(self, other):
         return self.x == other.x and self.y == other.y
    

    If you remove this __eq__ method, you'll get False, as in that case, Python default == operator on an unknown object only has object identity to perform comparisons.

    But the purpose of == is to compare object contents, not ids. Coding an equality method that tests identities can lead to surprises, as shown above.

    In Python when people use ==, they expect objects to be equal if values are equal. Identities are an implementation detail, just forget about it.

    (Previous versions of Python require you do define __ne__ as well, as it's not automatically the inverse of __eq__ and can lead to strange bugs)

    In a nutshell: don't use is (besides is None idiom) or id unless you're writing a very complex low-level program with caching and weird stuff or when debugging your program.