{"id":"class-examples-derived","slug":"derived-values","title":"Values worked out from others","text":"Often some of an object's values follow from others. A length of time in seconds also tells you how many hours and minutes it is. A good rule is to store the basic data in attributes and to work out the rest in methods, so that it is always up to date:\n\n```python\nclass Duration:\n    def __init__(self, seconds):\n        self.seconds = seconds\n\n    def hours(self):\n        return self.seconds // 3600\n\n    def minutes(self):\n        return self.seconds % 3600 // 60\n\n    def add(self, seconds):\n        self.seconds += seconds\n\n    def __str__(self):\n        return f\"{self.hours()}:{self.minutes():02}:{self.seconds % 60:02}\"\n\n\nfilm = Duration(5409)\nprint(film)\nfilm.add(600)\nprint(film)\nprint(film.hours(), film.minutes())\n```\n\n```output\n1:30:09\n1:40:09\n1 40\n```\n\nThe object stores only the total number of seconds. `add` changes that one attribute, and the hours and minutes are right after it, because they are worked out each time they are asked for.\n\nIn an f-string, `:02` after a whole number shows it with at least two digits, filling with zeros in front: `{9:02}` is `09`. Clock times need that.\n\nIf a class does store a value worked out from others, say to save working it out again and again, then every method that changes the data must update that value too. Forget one, and the object contradicts itself. That is why the rule above is a good one.\n\n### Values that wrap around\n\nSome values go around in a circle. An odometer with four digits goes from 9999 back to 0000, as a clock goes from 23:59 to 00:00. The remainder operator `%` keeps such a value in range:\n\n```python\nclass Odometer:\n    def __init__(self):\n        self.km = 0\n\n    def drive(self, km):\n        self.km = (self.km + km) % 10000\n\n    def __str__(self):\n        return f\"{self.km:04}\"\n\n\nold_van = Odometer()\nold_van.drive(9950)\nprint(old_van)\nold_van.drive(75)\nprint(old_van)\n```\n\n```output\n9950\n0025\n```\n\n9950 + 75 is 10025, and the remainder when that is divided by 10000 is 25. With `%`, the method can never leave the odometer out of range, whatever the client drives.\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Dial:\n    def __init__(self):\n        self.position = 0\n\n    def turn(self, steps):\n        self.position = (self.position + steps) % 12\n\n    def __str__(self):\n        return f\"position {self.position:02}\"\n\n\nd = Dial()\nd.turn(5)\nprint(d)\nd.turn(10)\nprint(d)\nd.turn(20)\nprint(d)\n","solution":"class Dial:\n    def __init__(self):\n        self.position = 0\n\n    def turn(self, steps):\n        self.position = (self.position + steps) % 12\n\n    def __str__(self):\n        return f\"position {self.position:02}\"\n\n\nd = Dial()\nd.turn(5)\nprint(d)\nd.turn(10)\nprint(d)\nd.turn(20)\nprint(d)\n","hints":["`print(d)` uses `__str__`, which shows the position with at least two digits.","After turning 5, the dial is at 5. Turning 10 more gives 15, and 15 % 12 is 3.","3 + 20 is 23, and 23 % 12 is 11."],"tests":[{"name":"Output","stdin":"","expect":"position 05\nposition 03\nposition 11","hidden":false}],"require":[],"forbid":[],"lines":["position 05","position 03","position 11"],"more":[{"task":"Write a class `Record` that keeps a team's wins and losses. Its constructor takes the starting numbers of wins and losses, both 0 when not given, and stores them in the attributes `wins` and `losses`. Give it these methods:\n\n- `win()` and `lose()` add one to the wins or the losses.\n- `games()` returns the number of games played.\n- `win_percent()` returns the share of games won as a whole number of percent (use `round`), or 0 when no games have been played.\n- `__str__` returns the wins, a hyphen, the losses and the percent, as shown below.\n\nThis code:\n\n```python\nteam = Record(6, 3)\nprint(team, team.games())\nteam.win()\nprint(team)\nrookies = Record()\nprint(rookies)\n```\n\nprints\n\n```\n6-3 (67%) 9\n7-3 (70%)\n0-0 (0%)\n```\n","kind":"code","seed":"class Record:\n    pass\n\n\nif __name__ == \"__main__\":\n    team = Record(6, 3)\n    print(team, team.games())\n","solution":"class Record:\n    def __init__(self, wins=0, losses=0):\n        self.wins = wins\n        self.losses = losses\n\n    def win(self):\n        self.wins += 1\n\n    def lose(self):\n        self.losses += 1\n\n    def games(self):\n        return self.wins + self.losses\n\n    def win_percent(self):\n        if self.games() == 0:\n            return 0\n        return round(self.wins / self.games() * 100)\n\n    def __str__(self):\n        return f\"{self.wins}-{self.losses} ({self.win_percent()}%)\"\n\n\nif __name__ == \"__main__\":\n    team = Record(6, 3)\n    print(team, team.games())\n","hints":["Store only the wins and the losses. `games` and `win_percent` work them out each time: `self.wins + self.losses`.","In `win_percent`, return 0 first when `self.games() == 0`, so that you never divide by zero.","`__str__` can call the other methods: `f\"{self.wins}-{self.losses} ({self.win_percent()}%)\"`."],"tests":[{"name":"Test 1","stdin":"","call":"team = Record(6, 3)\nprint(team, team.games())\nteam.win()\nprint(team)\nrookies = Record()\nprint(rookies)\n","expect":"6-3 (67%) 9\n7-3 (70%)\n0-0 (0%)","hidden":false},{"name":"Test 2","stdin":"","call":"r = Record()\nr.lose()\nr.lose()\nr.win()\nprint(r, r.games(), r.win_percent())\nprint(Record(1, 0), Record(0, 4), Record(2, 1))\n","expect":"1-2 (33%) 3 33\n1-0 (100%) 0-4 (0%) 2-1 (67%)","hidden":true}],"require":[],"forbid":[]},{"task":"Write a class `Countdown` for a kitchen timer. Its constructor takes the minutes and the seconds to start from. Give it these methods:\n\n- `tick()` takes one second off the time. When the time is already 00:00, it stays there.\n- `__str__` returns the time left as minutes and seconds, both with two digits: `02:05`.\n\nThis code:\n\n```python\ntimer = Countdown(1, 1)\nprint(timer)\ntimer.tick()\nprint(timer)\ntimer.tick()\nprint(timer)\n```\n\nprints\n\n```\n01:01\n01:00\n00:59\n```\n","kind":"code","seed":"class Countdown:\n    def __init__(self, minutes, seconds):\n        pass\n\n\nif __name__ == \"__main__\":\n    timer = Countdown(1, 1)\n    print(timer)\n    timer.tick()\n    print(timer)\n","solution":"class Countdown:\n    def __init__(self, minutes, seconds):\n        self.left = minutes * 60 + seconds\n\n    def tick(self):\n        if self.left > 0:\n            self.left -= 1\n\n    def __str__(self):\n        return f\"{self.left // 60:02}:{self.left % 60:02}\"\n\n\nif __name__ == \"__main__\":\n    timer = Countdown(1, 1)\n    print(timer)\n    timer.tick()\n    print(timer)\n","hints":["It's easiest to store just one number: the seconds left, `minutes * 60 + seconds`.","`tick` takes one off only `if` there is time left.","In `__str__`, the minutes are the seconds left `// 60` and the seconds are `% 60`. Show both with `:02`."],"tests":[{"name":"Test 1","stdin":"","call":"timer = Countdown(1, 1)\nprint(timer)\ntimer.tick()\nprint(timer)\ntimer.tick()\nprint(timer)\n","expect":"01:01\n01:00\n00:59","hidden":false},{"name":"Test 2","stdin":"","call":"egg = Countdown(0, 2)\nfor i in range(4):\n    print(egg)\n    egg.tick()\nprint(egg)\nprint(Countdown(12, 0), Countdown(0, 59), Countdown(5, 30))\n","expect":"00:02\n00:01\n00:00\n00:00\n00:00\n12:00 00:59 05:30","hidden":true},{"name":"Test 3","stdin":"","call":"long = Countdown(10, 0)\nfor i in range(61):\n    long.tick()\nprint(long)\n","expect":"08:59","hidden":true}],"require":[],"forbid":[]}]}