{"id":"oop-techniques-arithmetic","slug":"arithmetic","title":"Arithmetic operators","text":"The arithmetic operators can be overloaded too. `a + b` calls `a.__add__(b)`, and whatever that method returns is the result. These are the methods:\n\n| Operator | Meaning | Method |\n|---|---|---|\n| `+` | addition | `__add__(self, other)` |\n| `-` | subtraction | `__sub__(self, other)` |\n| `*` | multiplication | `__mul__(self, other)` |\n| `/` | division | `__truediv__(self, other)` |\n| `//` | integer division | `__floordiv__(self, other)` |\n\nHere's a class for durations, such as the length of a song. Adding two durations gives a new duration:\n\n```python\nclass Duration:\n    def __init__(self, minutes, seconds):\n        self.minutes = minutes\n        self.seconds = seconds\n\n    def __str__(self):\n        return f\"{self.minutes} min {self.seconds} s\"\n\n    def total_seconds(self):\n        return self.minutes * 60 + self.seconds\n\n    def __add__(self, other):\n        total = self.total_seconds() + other.total_seconds()\n        return Duration(total // 60, total % 60)\n\n\nintro = Duration(3, 40)\nsolo = Duration(4, 35)\nboth = intro + solo\nprint(both)\nprint(intro)\nprint(solo)\n```\n\n```output\n8 min 15 s\n3 min 40 s\n4 min 35 s\n```\n\n`__add__` returns a **new** object and leaves both of the originals as they were. That's what `+` does with numbers too: `2 + 3` doesn't change the 2. If `__add__` changed `self` instead, `both = intro + solo` would quietly change `intro` as well, and that's a surprise nobody wants.\n\n### The other operand can be something else\n\nThe two sides of an operator don't have to be of the same class. Multiplying a duration by a whole number makes sense: a song played three times. A difference that would be negative doesn't make sense, so `__sub__` raises a `ValueError`, as a class does when it's given a value it can't accept:\n\n```python\nclass Duration:\n    def __init__(self, minutes, seconds):\n        self.minutes = minutes\n        self.seconds = seconds\n\n    def __str__(self):\n        return f\"{self.minutes} min {self.seconds} s\"\n\n    def total_seconds(self):\n        return self.minutes * 60 + self.seconds\n\n    def __sub__(self, other):\n        total = self.total_seconds() - other.total_seconds()\n        if total < 0:\n            raise ValueError(\"a duration can't be negative\")\n        return Duration(total // 60, total % 60)\n\n    def __mul__(self, times):\n        total = self.total_seconds() * times\n        return Duration(total // 60, total % 60)\n\n\nintro = Duration(3, 40)\nsolo = Duration(4, 35)\nprint(intro * 3)\nprint(solo - intro)\nprint(intro - solo)\n```\n\n```output\n11 min 0 s\n0 min 55 s\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 27, in <module>\n    print(intro - solo)\n          ~~~~~~^~~~~~\n  File \"main.py\", line 15, in __sub__\n    raise ValueError(\"a duration can't be negative\")\nValueError: a duration can't be negative\n```\n\nIn `intro * 3`, `self` is `intro` and `times` is 3.\n\n### += comes for free\n\n`a += b` is short for `a = a + b`, so it works as soon as `__add__` does. It makes a new object and puts it in the variable. That's handy for a total:\n\n```python\ntotal = Duration(0, 0)\nfor song in album:\n    total += song\n```\n\nAs with comparisons, write only the operators that mean something for your class. Adding two durations makes sense; dividing a duration by another duration, maybe not.\n","task":"The class `Weight` can already add weights. Fill in the blank with the name of the method that makes `-` work, so the program prints:\n\n```\n700 g\n300 g\n500 g\n```\n","kind":"fill","seed":"class Weight:\n    def __init__(self, grams):\n        self.grams = grams\n\n    def __str__(self):\n        return f\"{self.grams} g\"\n\n    def __add__(self, other):\n        return Weight(self.grams + other.grams)\n\n    def [[__sub__]](self, other):\n        return Weight(self.grams - other.grams)\n\n\nif __name__ == \"__main__\":\n    flour = Weight(500)\n    sugar = Weight(200)\n    print(flour + sugar)\n    print(flour - sugar)\n    print(flour)\n","solution":"class Weight:\n    def __init__(self, grams):\n        self.grams = grams\n\n    def __str__(self):\n        return f\"{self.grams} g\"\n\n    def __add__(self, other):\n        return Weight(self.grams + other.grams)\n\n    def __sub__(self, other):\n        return Weight(self.grams - other.grams)\n\n\nif __name__ == \"__main__\":\n    flour = Weight(500)\n    sugar = Weight(200)\n    print(flour + sugar)\n    print(flour - sugar)\n    print(flour)\n","hints":["Look at the table: which method does Python call for `-`?","Subtraction is `sub`, with two underscores on both sides: `__sub__`."],"tests":[{"name":"Output","stdin":"","expect":"700 g\n300 g\n500 g","hidden":false}],"require":[],"forbid":[],"more":[{"task":"A vector is a step on a grid: `x` steps right and `y` steps up. Add three methods to the class `Vector`. Each returns a new vector and leaves the original ones as they are:\n\n- `+` adds two vectors: the new `x` is the sum of the two `x` values, and the same for `y`.\n- `-` subtracts the other vector the same way.\n- `*` multiplies a vector by a whole number: both `x` and `y` are multiplied by it.\n\nFor example,\n\n```python\na = Vector(2, 3)\nb = Vector(1, -1)\nprint(a + b)\nprint(a - b)\nprint(a * 3)\nprint(a)\n```\n\nshould print:\n\n```\n(3, 2)\n(1, 4)\n(6, 9)\n(2, 3)\n```\n","kind":"code","seed":"class Vector:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __str__(self):\n        return f\"({self.x}, {self.y})\"\n\n\nif __name__ == \"__main__\":\n    a = Vector(2, 3)\n    b = Vector(1, -1)\n    print(a + b)\n","solution":"class Vector:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __str__(self):\n        return f\"({self.x}, {self.y})\"\n\n    def __add__(self, other):\n        return Vector(self.x + other.x, self.y + other.y)\n\n    def __sub__(self, other):\n        return Vector(self.x - other.x, self.y - other.y)\n\n    def __mul__(self, number):\n        return Vector(self.x * number, self.y * number)\n\n\nif __name__ == \"__main__\":\n    a = Vector(2, 3)\n    b = Vector(1, -1)\n    print(a + b)\n","hints":["The methods are `__add__`, `__sub__` and `__mul__`. Each returns `Vector(...)` with the new `x` and `y`.","In `__add__`, the new vector is `Vector(self.x + other.x, self.y + other.y)`.","In `__mul__` the second parameter is a number, not a vector: `Vector(self.x * number, self.y * number)`."],"tests":[{"name":"Test 1","stdin":"","call":"a = Vector(2, 3)\nb = Vector(1, -1)\nprint(a + b)\nprint(a - b)\nprint(a * 3)\nprint(a)\n","expect":"(3, 2)\n(1, 4)\n(6, 9)\n(2, 3)","hidden":false},{"name":"Test 2","stdin":"","call":"start = Vector(0, 0)\nstep = Vector(-2, 5)\nposition = start + step * 4\nprint(position)\nprint(position - step)\nprint(step * 0)\nprint(start)\nprint(step)\n","expect":"(-8, 20)\n(-6, 15)\n(0, 0)\n(0, 0)\n(-2, 5)","hidden":true}],"require":[{"pattern":"def\\s+__add__\\s*\\(","message":"Write `__add__` for `+`."},{"pattern":"def\\s+__sub__\\s*\\(","message":"Write `__sub__` for `-`."},{"pattern":"def\\s+__mul__\\s*\\(","message":"Write `__mul__` for `*`."}],"forbid":[]},{"task":"The class `Clock` is a time of day. Add two operators to it:\n\n- `clock + minutes` returns a new clock that many minutes later. After 23:59 comes 0:00 again.\n- `clock - other` returns a whole number: how many minutes it takes to get from the time `other` to this clock's time, going forward, past midnight if needed.\n\nFor example,\n\n```python\nstart = Clock(22, 30)\nend = start + 150\nprint(end)\nprint(start)\nprint(end - start)\nprint(Clock(8, 15) - Clock(7, 50))\n```\n\nshould print:\n\n```\n1:00\n22:30\n150\n25\n```\n","kind":"code","seed":"class Clock:\n    def __init__(self, hours, minutes):\n        self.hours = hours\n        self.minutes = minutes\n\n    def __str__(self):\n        minutes = str(self.minutes)\n        if self.minutes < 10:\n            minutes = \"0\" + minutes\n        return f\"{self.hours}:{minutes}\"\n\n\nif __name__ == \"__main__\":\n    start = Clock(22, 30)\n    end = start + 150\n    print(end)\n    print(end - start)\n","solution":"class Clock:\n    def __init__(self, hours, minutes):\n        self.hours = hours\n        self.minutes = minutes\n\n    def __str__(self):\n        minutes = str(self.minutes)\n        if self.minutes < 10:\n            minutes = \"0\" + minutes\n        return f\"{self.hours}:{minutes}\"\n\n    def _total(self):\n        return self.hours * 60 + self.minutes\n\n    def __add__(self, minutes):\n        total = (self._total() + minutes) % (24 * 60)\n        return Clock(total // 60, total % 60)\n\n    def __sub__(self, other):\n        return (self._total() - other._total()) % (24 * 60)\n\n\nif __name__ == \"__main__\":\n    start = Clock(22, 30)\n    end = start + 150\n    print(end)\n    print(end - start)\n","hints":["Work in minutes after midnight: a helper method that returns `self.hours * 60 + self.minutes` makes both operators easier.","A day has `24 * 60` minutes. In `__add__`, `% (24 * 60)` takes the total back to the start of the day after midnight; then `total // 60` is the hours and `total % 60` the minutes.","`__sub__` returns a number, not a clock. The difference of the two times in minutes can be negative when midnight is in between, and `% (24 * 60)` turns it into the right number of minutes forward, since `%` in Python never gives a negative result for a positive divisor."],"tests":[{"name":"Test 1","stdin":"","call":"start = Clock(22, 30)\nend = start + 150\nprint(end)\nprint(start)\nprint(end - start)\nprint(Clock(8, 15) - Clock(7, 50))\n","expect":"1:00\n22:30\n150\n25","hidden":false},{"name":"Test 2","stdin":"","call":"alarm = Clock(6, 45)\nprint(alarm + 20)\nprint(alarm + 24 * 60)\nprint(alarm + 1000)\nprint(Clock(0, 5) - Clock(23, 55))\nprint(alarm - alarm)\nprint(Clock(12, 0) - Clock(6, 45))\n","expect":"7:05\n6:45\n23:25\n10\n0\n315","hidden":true}],"require":[{"pattern":"def\\s+__add__\\s*\\(","message":"Write `__add__` for `+`."},{"pattern":"def\\s+__sub__\\s*\\(","message":"Write `__sub__` for `-`."}],"forbid":[]}]}