Python Arena
Step 2 of 5

Equality with __eq__

For ==, Python has an answer even when your class has no method for it. But it's probably not the answer you want:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


a = Point(1, 2)
b = Point(1, 2)
c = a
print(a == b)
print(a == c)
Output
False
True

Without a method of its own, == asks whether the two are the very same object, as is does. a and c refer to the same point, but b is a different object, even though it has the same coordinates. To compare the contents, write the method __eq__:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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


a = Point(1, 2)
b = Point(1, 2)
print(a == b)
print(a != b)
print(Point(3, 4) in [a, b, Point(3, 4)])
Output
True
False
True

Two things come for free. When a class has __eq__ but no __ne__, Python works out != as the opposite of ==. And everything that compares items with == now works with points: in for lists, and the list methods count, index and remove.

Comparing with something else

__eq__ above expects other to be a point. Compared with anything else, say the string "1, 2", it would stop with an AttributeError, since a string has no x. A careful __eq__ first checks the type with isinstance, and returns False for anything that isn't a point:

def __eq__(self, other):
    if not isinstance(other, Point):
        return False
    return self.x == other.x and self.y == other.y

A full set of comparisons

<= doesn't come from < and == automatically: write __le__ for it, and __ge__ for >=. (When one side has no method for an operator, Python tries the mirrored method of the other side: for b >= a it can use a.__le__(b). Still, writing a method for each operator you use is the clear way.) The new methods can be written with the ones you have. Software versions such as 3.12 and 3.14 are a good example. The major version, the first number, matters most, and the minor version only decides when the major versions are equal:

class Version:
    def __init__(self, major, minor):
        self.major = major
        self.minor = minor

    def __eq__(self, other):
        return self.major == other.major and self.minor == other.minor

    def __lt__(self, other):
        if self.major != other.major:
            return self.major < other.major
        return self.minor < other.minor

    def __le__(self, other):
        return self < other or self == other


old = Version(3, 12)
new = Version(3, 14)
print(old < new)
print(new <= Version(3, 14))
print(Version(2, 99) < old)
Output
True
True
True

Note how __le__ uses self < other and self == other: inside the class, the operators call the class's own methods too.

Your task

Challenge 1 of 3

What does this program print? Type each line it prints.

Challenge 2

Add comparisons to the class Time, a time of day in hours and minutes. Write the methods for ==, <, <=, > and >=. The protected helper _minutes() is already there: it gives the time as minutes after midnight, which makes every comparison easy. For example,

lunch = Time(12, 30)
meeting = Time(9, 45)
also_lunch = Time(12, 30)
print(lunch == also_lunch)
print(lunch != meeting)
print(meeting < lunch)
print(lunch <= also_lunch)
print(meeting >= lunch)
print(lunch > meeting)

should print:

True
True
True
True
False
True

Challenge 3

A card has a rank and a suit, and two cards are equal when both their rank and their suit are the same. Add the method __eq__ to the class Card. A card compared with something that isn't a card is not equal to it: that should give False, not an error. With __eq__ in place, lists of cards work with in and count too. For example,

hand = [Card("K", "hearts"), Card("7", "clubs"), Card("K", "hearts")]
print(Card("K", "hearts") == Card("K", "hearts"))
print(Card("K", "hearts") == Card("K", "spades"))
print(Card("7", "clubs") in hand)
print(hand.count(Card("K", "hearts")))
print(Card("7", "clubs") == "7 of clubs")

should print:

True
False
True
2
False