{"id":"oop-techniques-comparisons","slug":"comparisons","title":"Comparing objects with operators","text":"A method can return an object, including an object of its own class. And since `self` is the object itself, a method can return `self` too. Here `heavier` compares two parcels and returns the heavier one, whichever it is:\n\n```python\nclass Parcel:\n    def __init__(self, label, weight):\n        self.label = label\n        self.weight = weight\n\n    def __str__(self):\n        return f\"{self.label} ({self.weight} kg)\"\n\n    def heavier(self, other):\n        if self.weight > other.weight:\n            return self\n        return other\n\n\nbooks = Parcel(\"Books\", 12)\npillow = Parcel(\"Pillow\", 2)\nprint(books.heavier(pillow))\nprint(pillow.heavier(books))\n```\n\n```output\nBooks (12 kg)\nBooks (12 kg)\n```\n\nThat works, but it would be nicer to compare parcels the way you compare numbers, with `>` and `<`. As it is, Python doesn't know how:\n\n```python\nclass Parcel:\n    def __init__(self, label, weight):\n        self.label = label\n        self.weight = weight\n\n\nbooks = Parcel(\"Books\", 12)\npillow = Parcel(\"Pillow\", 2)\nprint(books > pillow)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 9, in <module>\n    print(books > pillow)\n          ^^^^^^^^^^^^^^\nTypeError: '>' not supported between instances of 'Parcel' and 'Parcel'\n```\n\n### Operator overloading\n\nA class can decide what an operator means for its objects. This is called **operator overloading**. Each operator has a method with a special name, with two underscores on both sides, like `__init__` and `__str__`. When Python meets `a > b` and `a` is an object of your class, it calls `a.__gt__(b)` (\"gt\" for greater than). The method returns `True` or `False`:\n\n```python\nclass Parcel:\n    def __init__(self, label, weight):\n        self.label = label\n        self.weight = weight\n\n    def __str__(self):\n        return f\"{self.label} ({self.weight} kg)\"\n\n    def __gt__(self, other):\n        return self.weight > other.weight\n\n    def __lt__(self, other):\n        return self.weight < other.weight\n\n\nbooks = Parcel(\"Books\", 12)\npillow = Parcel(\"Pillow\", 2)\nlamp = Parcel(\"Lamp\", 5)\nprint(books > pillow)\nprint(books < pillow)\n\nparcels = [books, pillow, lamp]\nfor parcel in sorted(parcels):\n    print(parcel)\nprint(\"Heaviest:\", max(parcels))\n```\n\n```output\nTrue\nFalse\nPillow (2 kg)\nLamp (5 kg)\nBooks (12 kg)\nHeaviest: Books (12 kg)\n```\n\nYou decide what \"greater\" means. Here it's the weight; for another class it could be a price, a date, or the alphabetical order of names. And there's a bonus: `sorted`, `min` and `max` compare items with `<` and `>`, so they work with your objects too, as soon as the class has these methods.\n\nThese are the comparison operators and their methods:\n\n| Operator | Meaning | Method |\n|---|---|---|\n| `<` | less than | `__lt__(self, other)` |\n| `>` | greater than | `__gt__(self, other)` |\n| `<=` | less than or equal to | `__le__(self, other)` |\n| `>=` | greater than or equal to | `__ge__(self, other)` |\n| `==` | equal to | `__eq__(self, other)` |\n| `!=` | not equal to | `__ne__(self, other)` |\n\nYou only need to write the ones that make sense for your class. The next step looks at `==`, which works a little differently.\n","task":"The faster runner is the one with fewer seconds. Fill in the blank with the name of the method that makes `<` work for runners, so the program prints:\n\n```\nTrue\nFalse\n```\n","kind":"fill","seed":"class Runner:\n    def __init__(self, name, seconds):\n        self.name = name\n        self.seconds = seconds\n\n    def [[__lt__]](self, other):\n        return self.seconds < other.seconds\n\n\nif __name__ == \"__main__\":\n    ada = Runner(\"Ada\", 61)\n    ben = Runner(\"Ben\", 58)\n    print(ben < ada)\n    print(ada < ben)\n","solution":"class Runner:\n    def __init__(self, name, seconds):\n        self.name = name\n        self.seconds = seconds\n\n    def __lt__(self, other):\n        return self.seconds < other.seconds\n\n\nif __name__ == \"__main__\":\n    ada = Runner(\"Ada\", 61)\n    ben = Runner(\"Ben\", 58)\n    print(ben < ada)\n    print(ada < ben)\n","hints":["Look at the table: which method does Python call for `<`?","\"Less than\" is `lt`, with two underscores on both sides: `__lt__`."],"tests":[{"name":"Output","stdin":"","expect":"True\nFalse","hidden":false}],"require":[],"forbid":[],"more":[{"task":"A room is bigger than another when its floor area, width times length, is bigger. Add the methods `__lt__` and `__gt__` to the class `Room`, so that rooms can be compared with `<` and `>`, and `max` finds the biggest room. For example,\n\n```python\nkitchen = Room(\"Kitchen\", 3, 4)\nhall = Room(\"Hall\", 2, 5)\nprint(kitchen > hall)\nprint(kitchen < hall)\nprint(hall < kitchen)\nprint(max([hall, kitchen]).name)\n```\n\nshould print:\n\n```\nTrue\nFalse\nTrue\nKitchen\n```\n","kind":"code","seed":"class Room:\n    def __init__(self, name, width, length):\n        self.name = name\n        self.width = width\n        self.length = length\n\n    def area(self):\n        return self.width * self.length\n\n\nif __name__ == \"__main__\":\n    kitchen = Room(\"Kitchen\", 3, 4)\n    hall = Room(\"Hall\", 2, 5)\n    print(kitchen > hall)\n","solution":"class Room:\n    def __init__(self, name, width, length):\n        self.name = name\n        self.width = width\n        self.length = length\n\n    def area(self):\n        return self.width * self.length\n\n    def __lt__(self, other):\n        return self.area() < other.area()\n\n    def __gt__(self, other):\n        return self.area() > other.area()\n\n\nif __name__ == \"__main__\":\n    kitchen = Room(\"Kitchen\", 3, 4)\n    hall = Room(\"Hall\", 2, 5)\n    print(kitchen > hall)\n","hints":["Both methods take `self` and `other`, another room, and return `True` or `False`.","Compare the areas: `return self.area() < other.area()` in `__lt__`, and the same with `>` in `__gt__`."],"tests":[{"name":"Test 1","stdin":"","call":"kitchen = Room(\"Kitchen\", 3, 4)\nhall = Room(\"Hall\", 2, 5)\nprint(kitchen > hall)\nprint(kitchen < hall)\nprint(hall < kitchen)\nprint(max([hall, kitchen]).name)\n","expect":"True\nFalse\nTrue\nKitchen","hidden":false},{"name":"Test 2","stdin":"","call":"attic = Room(\"Attic\", 6, 2)\nstudio = Room(\"Studio\", 3, 5)\ncloset = Room(\"Closet\", 1, 2)\nprint(attic > studio)\nprint(attic < studio)\nprint(closet < attic)\nrooms = [attic, studio, closet]\nprint(max(rooms).name)\nprint(min(rooms).name)\nfor room in sorted(rooms):\n    print(room.name)\n","expect":"False\nTrue\nTrue\nStudio\nCloset\nCloset\nAttic\nStudio","hidden":true}],"require":[{"pattern":"def\\s+__lt__\\s*\\(","message":"Write the method `__lt__(self, other)` for `<`."},{"pattern":"def\\s+__gt__\\s*\\(","message":"Write the method `__gt__(self, other)` for `>`."}],"forbid":[]},{"task":"A color on a screen is made of red, green and blue light, each a whole number from 0 to 255. Add two methods to the class `Color`. Both return a **new** color and leave the original as it is:\n\n- `mix(other)` returns a color whose red is the average of the two colors' reds, rounded down, and the same for green and blue.\n- `brighter()` returns a color with 50 more of each, but never more than 255. (`min` works with two numbers too: `min(a, b)` is the smaller of them.)\n\nFor example,\n\n```python\nred = Color(255, 0, 0)\nblue = Color(0, 0, 255)\nprint(red.mix(blue))\nprint(red.brighter())\nprint(red)\n```\n\nshould print:\n\n```\nrgb(127, 0, 127)\nrgb(255, 50, 50)\nrgb(255, 0, 0)\n```\n","kind":"code","seed":"class Color:\n    def __init__(self, red, green, blue):\n        self.red = red\n        self.green = green\n        self.blue = blue\n\n    def __str__(self):\n        return f\"rgb({self.red}, {self.green}, {self.blue})\"\n\n\nif __name__ == \"__main__\":\n    red = Color(255, 0, 0)\n    blue = Color(0, 0, 255)\n    print(red.mix(blue))\n    print(red.brighter())\n","solution":"class Color:\n    def __init__(self, red, green, blue):\n        self.red = red\n        self.green = green\n        self.blue = blue\n\n    def __str__(self):\n        return f\"rgb({self.red}, {self.green}, {self.blue})\"\n\n    def mix(self, other):\n        red = (self.red + other.red) // 2\n        green = (self.green + other.green) // 2\n        blue = (self.blue + other.blue) // 2\n        return Color(red, green, blue)\n\n    def brighter(self):\n        return Color(min(self.red + 50, 255), min(self.green + 50, 255), min(self.blue + 50, 255))\n\n\nif __name__ == \"__main__\":\n    red = Color(255, 0, 0)\n    blue = Color(0, 0, 255)\n    print(red.mix(blue))\n    print(red.brighter())\n","hints":["A method can create an object of its own class: `return Color(...)`, with the new red, green and blue in the parentheses.","The average rounded down is `(self.red + other.red) // 2`. Don't change `self.red`: a new color gets the new values.","In `brighter`, `min(self.red + 50, 255)` is the smaller of the two, so it's never more than 255."],"tests":[{"name":"Test 1","stdin":"","call":"red = Color(255, 0, 0)\nblue = Color(0, 0, 255)\nprint(red.mix(blue))\nprint(red.brighter())\nprint(red)\n","expect":"rgb(127, 0, 127)\nrgb(255, 50, 50)\nrgb(255, 0, 0)","hidden":false},{"name":"Test 2","stdin":"","call":"sky = Color(100, 180, 240)\ngrass = Color(41, 120, 9)\nprint(sky.mix(grass))\nprint(grass.mix(sky))\nprint(sky.brighter())\nprint(sky.brighter().brighter())\nprint(sky)\nprint(grass)\n","expect":"rgb(70, 150, 124)\nrgb(70, 150, 124)\nrgb(150, 230, 255)\nrgb(200, 255, 255)\nrgb(100, 180, 240)\nrgb(41, 120, 9)","hidden":true}],"require":[],"forbid":[]}]}