{"id":"methods-state","slug":"changing-state","title":"Methods that change the object","text":"The values of an object's attributes at any moment are called its **state**. A method can change the state, and it's the usual way to do it. Here is a counter:\n\n```python\nclass Counter:\n    def __init__(self):\n        self.value = 0\n\n    def increase(self):\n        self.value += 1\n\n    def add(self, amount):\n        self.value += amount\n\n    def reset(self):\n        self.value = 0\n\n\nclicks = Counter()\nclicks.increase()\nclicks.increase()\nclicks.add(10)\nprint(clicks.value)\nclicks.reset()\nprint(clicks.value)\n```\n\n```output\n12\n0\n```\n\nThese methods don't return anything: their whole job is to change the object. They change the object that they are called on, and only that one:\n\n```python\nclass Counter:\n    def __init__(self):\n        self.value = 0\n\n    def increase(self):\n        self.value += 1\n\n\nhome = Counter()\naway = Counter()\nhome.increase()\nhome.increase()\naway.increase()\nprint(home.value, away.value)\n```\n\n```output\n2 1\n```\n\n`home.increase()` runs the method with `self` as `home`, so `self.value += 1` adds to the home counter. The away counter has a value of its own.\n\n### One call, every update\n\nA method can update several attributes at once, so that they always fit together. A car that drives also uses fuel:\n\n```python\nclass Car:\n    def __init__(self, fuel, consumption):\n        self.fuel = fuel\n        self.consumption = consumption\n        self.odometer = 0\n\n    def drive(self, km):\n        self.odometer += km\n        self.fuel -= km * self.consumption / 100\n\n    def km_left(self):\n        return self.fuel / self.consumption * 100\n\n\ncar = Car(40, 5)\ncar.drive(120)\ncar.drive(80)\nprint(car.odometer, car.fuel)\nprint(car.km_left())\n```\n\n```output\n200 30.0\n600.0\n```\n\n(`fuel` is in liters and `consumption` in liters per 100 km.) The rest of the program only says `car.drive(120)`. It doesn't need to know how the fuel is worked out, and it can't forget to update one of the attributes. This is the big idea of object-oriented programming: the class takes care of its own data.\n\nA method can also change the state and return something, as the list method `pop` does: it removes an item and gives it to you.\n\n### Naming methods\n\nName a method after what it does. A method that changes the object usually gets a verb: `increase`, `drive`, `reset`. A method that only gives an answer is named after the answer, such as `area` or `km_left`, or it starts with `is_` when the answer is yes or no: `is_empty`. Then the client code reads almost like a sentence: `car.drive(120)`, `if counter.is_empty():`.\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Lamp:\n    def __init__(self):\n        self.on = False\n        self.switches = 0\n\n    def toggle(self):\n        self.on = not self.on\n        self.switches += 1\n\n\ndesk = Lamp()\nhall = Lamp()\ndesk.toggle()\ndesk.toggle()\nhall.toggle()\nprint(desk.on, desk.switches)\nprint(hall.on, hall.switches)\n","solution":"class Lamp:\n    def __init__(self):\n        self.on = False\n        self.switches = 0\n\n    def toggle(self):\n        self.on = not self.on\n        self.switches += 1\n\n\ndesk = Lamp()\nhall = Lamp()\ndesk.toggle()\ndesk.toggle()\nhall.toggle()\nprint(desk.on, desk.switches)\nprint(hall.on, hall.switches)\n","hints":["Every lamp starts off, with no switches, and each lamp keeps its own state.","`not self.on` turns `False` into `True` and back, so two toggles bring the desk lamp back to off.","The hall lamp was toggled once."],"tests":[{"name":"Output","stdin":"","expect":"False 2\nTrue 1","hidden":false}],"require":[],"forbid":[],"lines":["False 2","True 1"],"more":[{"task":"Write a class `PiggyBank`. A new piggy bank has the attribute `cents` set to 0. Give it these methods:\n\n- `insert(cents)` adds the coins to the bank.\n- `euros()` returns how many euros the bank holds: the cents divided by 100.\n- `smash()` returns all the cents in the bank, and leaves the bank empty.\n\nThis code:\n\n```python\nbank = PiggyBank()\nbank.insert(50)\nbank.insert(200)\nprint(bank.cents, bank.euros())\nprint(bank.smash())\nprint(bank.cents)\n```\n\nprints\n\n```\n250 2.5\n250\n0\n```\n","kind":"code","seed":"class PiggyBank:\n    pass\n\n\nif __name__ == \"__main__\":\n    bank = PiggyBank()\n    bank.insert(50)\n    print(bank.cents)\n","solution":"class PiggyBank:\n    def __init__(self):\n        self.cents = 0\n\n    def insert(self, cents):\n        self.cents += cents\n\n    def euros(self):\n        return self.cents / 100\n\n    def smash(self):\n        amount = self.cents\n        self.cents = 0\n        return amount\n\n\nif __name__ == \"__main__\":\n    bank = PiggyBank()\n    bank.insert(50)\n    print(bank.cents)\n","hints":["The constructor has only `self` as a parameter and sets `self.cents = 0`.","`insert` adds its parameter to `self.cents`, and `euros` returns `self.cents / 100`.","`smash` has to remember the amount before it empties the bank: store `self.cents` in a variable, set `self.cents = 0`, then return the variable."],"tests":[{"name":"Test 1","stdin":"","call":"bank = PiggyBank()\nbank.insert(50)\nbank.insert(200)\nprint(bank.cents, bank.euros())\nprint(bank.smash())\nprint(bank.cents)\n","expect":"250 2.5\n250\n0","hidden":false},{"name":"Test 2","stdin":"","call":"mine = PiggyBank()\nyours = PiggyBank()\nmine.insert(20)\nyours.insert(500)\nmine.insert(5)\nprint(mine.euros(), yours.euros())\nprint(yours.smash(), yours.smash())\nprint(mine.cents, yours.cents)\n","expect":"0.25 5.0\n500 0\n25 0","hidden":true}],"require":[],"forbid":[]},{"task":"Write a class `Reading` that keeps track of reading a book. Its constructor takes the book's title and its number of pages, and stores them in the attributes `title` and `pages`. It also sets the attribute `page`, the page the reader is on, to 0. Give it these methods:\n\n- `read(pages)` moves the reader forward that many pages. (You can assume it never goes past the end.)\n- `percent()` returns how much of the book has been read, as a whole number of percent: use `round`.\n- `finished()` returns `True` when the reader is on the last page.\n\nThis code:\n\n```python\nbook = Reading(\"Moby-Dick\", 600)\nbook.read(150)\nprint(book.page, book.percent(), book.finished())\nbook.read(400)\nbook.read(50)\nprint(book.page, book.percent(), book.finished())\n```\n\nprints\n\n```\n150 25 False\n600 100 True\n```\n","kind":"code","seed":"class Reading:\n    def __init__(self, title, pages):\n        self.title = title\n\n\nif __name__ == \"__main__\":\n    book = Reading(\"Moby-Dick\", 600)\n    book.read(150)\n    print(book.page, book.percent(), book.finished())\n","solution":"class Reading:\n    def __init__(self, title, pages):\n        self.title = title\n        self.pages = pages\n        self.page = 0\n\n    def read(self, pages):\n        self.page += pages\n\n    def percent(self):\n        return round(self.page / self.pages * 100)\n\n    def finished(self):\n        return self.page == self.pages\n\n\nif __name__ == \"__main__\":\n    book = Reading(\"Moby-Dick\", 600)\n    book.read(150)\n    print(book.page, book.percent(), book.finished())\n","hints":["The constructor stores both arguments and sets `self.page = 0`.","The method's parameter `pages` and the attribute `self.pages` are different variables: `read` adds the parameter to `self.page`.","`percent` returns `round(self.page / self.pages * 100)`, and `finished` compares `self.page` with `self.pages`."],"tests":[{"name":"Test 1","stdin":"","call":"book = Reading(\"Moby-Dick\", 600)\nbook.read(150)\nprint(book.page, book.percent(), book.finished())\nbook.read(400)\nbook.read(50)\nprint(book.page, book.percent(), book.finished())\n","expect":"150 25 False\n600 100 True","hidden":false},{"name":"Test 2","stdin":"","call":"short = Reading(\"The Lottery\", 9)\nprint(short.title, short.pages, short.page, short.percent())\nshort.read(2)\nprint(short.percent(), short.finished())\nshort.read(7)\nprint(short.percent(), short.finished())\n","expect":"The Lottery 9 0 0\n22 False\n100 True","hidden":true}],"require":[],"forbid":[]}]}