{"id":"oop-techniques-equality","slug":"equality","title":"Equality with __eq__","text":"For `==`, Python has an answer even when your class has no method for it. But it's probably not the answer you want:\n\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\na = Point(1, 2)\nb = Point(1, 2)\nc = a\nprint(a == b)\nprint(a == c)\n```\n\n```output\nFalse\nTrue\n```\n\nWithout 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__`**:\n\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __eq__(self, other):\n        return self.x == other.x and self.y == other.y\n\n\na = Point(1, 2)\nb = Point(1, 2)\nprint(a == b)\nprint(a != b)\nprint(Point(3, 4) in [a, b, Point(3, 4)])\n```\n\n```output\nTrue\nFalse\nTrue\n```\n\nTwo 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`.\n\n### Comparing with something else\n\n`__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:\n\n```python\ndef __eq__(self, other):\n    if not isinstance(other, Point):\n        return False\n    return self.x == other.x and self.y == other.y\n```\n\n### A full set of comparisons\n\n`<=` 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:\n\n```python\nclass Version:\n    def __init__(self, major, minor):\n        self.major = major\n        self.minor = minor\n\n    def __eq__(self, other):\n        return self.major == other.major and self.minor == other.minor\n\n    def __lt__(self, other):\n        if self.major != other.major:\n            return self.major < other.major\n        return self.minor < other.minor\n\n    def __le__(self, other):\n        return self < other or self == other\n\n\nold = Version(3, 12)\nnew = Version(3, 14)\nprint(old < new)\nprint(new <= Version(3, 14))\nprint(Version(2, 99) < old)\n```\n\n```output\nTrue\nTrue\nTrue\n```\n\nNote how `__le__` uses `self < other` and `self == other`: inside the class, the operators call the class's own methods too.\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Coin:\n    def __init__(self, value):\n        self.value = value\n\n\nclass Bill:\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        return self.value == other.value\n\n\nprint(Coin(5) == Coin(5))\nprint(Bill(5) == Bill(5))\nprint(Bill(5) != Bill(10))\na = Coin(1)\nb = a\nprint(a == b)\n","solution":"class Coin:\n    def __init__(self, value):\n        self.value = value\n\n\nclass Bill:\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        return self.value == other.value\n\n\nprint(Coin(5) == Coin(5))\nprint(Bill(5) == Bill(5))\nprint(Bill(5) != Bill(10))\na = Coin(1)\nb = a\nprint(a == b)\n","hints":["`Coin` has no `__eq__`, so `==` asks whether the two are the very same object. `Bill` compares the values.","Two separate `Coin(5)` objects are not the same object. But `b = a` doesn't make a new coin: `a` and `b` refer to the same one.","With `__eq__` and no `__ne__`, `!=` is the opposite of `==`."],"tests":[{"name":"Output","stdin":"","expect":"False\nTrue\nTrue\nTrue","hidden":false}],"require":[],"forbid":[],"lines":["False","True","True","True"],"more":[{"task":"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,\n\n```python\nlunch = Time(12, 30)\nmeeting = Time(9, 45)\nalso_lunch = Time(12, 30)\nprint(lunch == also_lunch)\nprint(lunch != meeting)\nprint(meeting < lunch)\nprint(lunch <= also_lunch)\nprint(meeting >= lunch)\nprint(lunch > meeting)\n```\n\nshould print:\n\n```\nTrue\nTrue\nTrue\nTrue\nFalse\nTrue\n```\n","kind":"code","seed":"class Time:\n    def __init__(self, hours, minutes):\n        self.hours = hours\n        self.minutes = minutes\n\n    def _minutes(self):\n        return self.hours * 60 + self.minutes\n\n\nif __name__ == \"__main__\":\n    lunch = Time(12, 30)\n    meeting = Time(9, 45)\n    print(meeting < lunch)\n","solution":"class Time:\n    def __init__(self, hours, minutes):\n        self.hours = hours\n        self.minutes = minutes\n\n    def _minutes(self):\n        return self.hours * 60 + self.minutes\n\n    def __eq__(self, other):\n        return self._minutes() == other._minutes()\n\n    def __lt__(self, other):\n        return self._minutes() < other._minutes()\n\n    def __le__(self, other):\n        return self._minutes() <= other._minutes()\n\n    def __gt__(self, other):\n        return self._minutes() > other._minutes()\n\n    def __ge__(self, other):\n        return self._minutes() >= other._minutes()\n\n\nif __name__ == \"__main__\":\n    lunch = Time(12, 30)\n    meeting = Time(9, 45)\n    print(meeting < lunch)\n","hints":["The methods are `__eq__`, `__lt__`, `__le__`, `__gt__` and `__ge__`, each with the parameters `self` and `other`. `!=` comes for free from `__eq__`.","Each one compares the two times in minutes: `return self._minutes() < other._minutes()` for `__lt__`, and the other operators the same way.","`other` is a `Time` too, so it has `_minutes()` as well. Using a protected method of another object of the same class is fine."],"tests":[{"name":"Test 1","stdin":"","call":"lunch = Time(12, 30)\nmeeting = Time(9, 45)\nalso_lunch = Time(12, 30)\nprint(lunch == also_lunch)\nprint(lunch != meeting)\nprint(meeting < lunch)\nprint(lunch <= also_lunch)\nprint(meeting >= lunch)\nprint(lunch > meeting)\n","expect":"True\nTrue\nTrue\nTrue\nFalse\nTrue","hidden":false},{"name":"Test 2","stdin":"","call":"early = Time(6, 59)\nlate = Time(7, 0)\nprint(early == late)\nprint(early < late)\nprint(early > late)\nprint(late >= early)\nprint(late <= early)\nprint(early >= Time(6, 59))\nprint(sorted([late, early])[0].hours)\n","expect":"False\nTrue\nFalse\nTrue\nFalse\nTrue\n6","hidden":true}],"require":[{"pattern":"def\\s+__eq__\\s*\\(","message":"Write `__eq__` for `==`."},{"pattern":"def\\s+__le__\\s*\\(","message":"Write `__le__` for `<=`."},{"pattern":"def\\s+__ge__\\s*\\(","message":"Write `__ge__` for `>=`."}],"forbid":[]},{"task":"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,\n\n```python\nhand = [Card(\"K\", \"hearts\"), Card(\"7\", \"clubs\"), Card(\"K\", \"hearts\")]\nprint(Card(\"K\", \"hearts\") == Card(\"K\", \"hearts\"))\nprint(Card(\"K\", \"hearts\") == Card(\"K\", \"spades\"))\nprint(Card(\"7\", \"clubs\") in hand)\nprint(hand.count(Card(\"K\", \"hearts\")))\nprint(Card(\"7\", \"clubs\") == \"7 of clubs\")\n```\n\nshould print:\n\n```\nTrue\nFalse\nTrue\n2\nFalse\n```\n","kind":"code","seed":"class Card:\n    def __init__(self, rank, suit):\n        self.rank = rank\n        self.suit = suit\n\n    def __str__(self):\n        return f\"{self.rank} of {self.suit}\"\n\n\nif __name__ == \"__main__\":\n    hand = [Card(\"K\", \"hearts\"), Card(\"7\", \"clubs\")]\n    print(Card(\"7\", \"clubs\") in hand)\n","solution":"class Card:\n    def __init__(self, rank, suit):\n        self.rank = rank\n        self.suit = suit\n\n    def __str__(self):\n        return f\"{self.rank} of {self.suit}\"\n\n    def __eq__(self, other):\n        if not isinstance(other, Card):\n            return False\n        return self.rank == other.rank and self.suit == other.suit\n\n\nif __name__ == \"__main__\":\n    hand = [Card(\"K\", \"hearts\"), Card(\"7\", \"clubs\")]\n    print(Card(\"7\", \"clubs\") in hand)\n","hints":["`__eq__(self, other)` returns `True` when the rank and the suit are both the same: combine the two comparisons with `and`.","Check the type first: `if not isinstance(other, Card):` then `return False`. A string has no `rank`, so without the check it would stop with an error."],"tests":[{"name":"Test 1","stdin":"","call":"hand = [Card(\"K\", \"hearts\"), Card(\"7\", \"clubs\"), Card(\"K\", \"hearts\")]\nprint(Card(\"K\", \"hearts\") == Card(\"K\", \"hearts\"))\nprint(Card(\"K\", \"hearts\") == Card(\"K\", \"spades\"))\nprint(Card(\"7\", \"clubs\") in hand)\nprint(hand.count(Card(\"K\", \"hearts\")))\nprint(Card(\"7\", \"clubs\") == \"7 of clubs\")\n","expect":"True\nFalse\nTrue\n2\nFalse","hidden":false},{"name":"Test 2","stdin":"","call":"deck = [Card(\"A\", \"spades\"), Card(\"2\", \"spades\"), Card(\"A\", \"diamonds\")]\nprint(Card(\"A\", \"spades\") != Card(\"A\", \"diamonds\"))\nprint(Card(\"2\", \"diamonds\") in deck)\ndeck.remove(Card(\"2\", \"spades\"))\nfor card in deck:\n    print(card)\nprint(Card(\"A\", \"spades\") == 1)\n","expect":"True\nFalse\nA of spades\nA of diamonds\nFalse","hidden":true}],"require":[{"pattern":"def\\s+__eq__\\s*\\(","message":"Write the method `__eq__(self, other)`."},{"pattern":"\\bisinstance\\s*\\(","message":"Check with `isinstance` that `other` is a card before you compare its rank and suit."}],"forbid":[]}]}