0

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()

Don Cheeto
  • 111
  • 1
  • 9

2 Answers2

2

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.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
0

Python caches small integers (in range [-5, 256]), thus id(self.x) == id(other.x) and id(self.y) == id(other.y) is True. Since self.x and other.x are the same objects in memory. Find a different way of comparing those two objects or get rid of your custom __eq__ and use the default way (Python will return False for point == Point(2, 3) in that case).
See this answer for more on the issue.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93