{"id":"class-attributes-defaults","slug":"default-values","title":"Default values for parameters","text":"In part 7 you gave a function's parameters **default values**, used whenever the caller leaves the argument out. Methods and constructors can have them too. They're handy when most objects start the same way:\n\n```python\nclass Character:\n    def __init__(self, name, health=100, level=1):\n        self.name = name\n        self.health = health\n        self.level = level\n\n    def heal(self, amount=10):\n        self.health += amount\n\n    def __str__(self):\n        return f\"{self.name}: health {self.health}, level {self.level}\"\n\n\nana = Character(\"Ana\")\nbo = Character(\"Bo\", 80)\ncy = Character(\"Cy\", level=5)\nbo.heal()\nbo.heal(5)\nprint(ana)\nprint(bo)\nprint(cy)\n```\n\n```output\nAna: health 100, level 1\nBo: health 95, level 1\nCy: health 100, level 5\n```\n\nArguments fill the parameters in order, so `Character(\"Bo\", 80)` sets the health. To give a later parameter and skip an earlier one, name it: `Character(\"Cy\", level=5)` is a **keyword argument**. Writing `Character(\"Cy\", 5)` would set the health to 5, not the level.\n\nParameters with default values come after the ones without. `def __init__(self, health=100, name):` is a syntax error: `parameter without a default follows parameter with a default`.\n\n### The trap: a list as a default value\n\nA default value is created only once, when Python reads the `def` line, not on every call. For a number or a string that never matters. For a list it does, because a list can change. Every object that gets the default ends up with *the same list*:\n\n```python\nclass Backpack:\n    def __init__(self, owner, items=[]):\n        self.owner = owner\n        self.items = items\n\n    def pack(self, item):\n        self.items.append(item)\n\n\nana = Backpack(\"Ana\")\nbo = Backpack(\"Bo\")\nana.pack(\"map\")\nprint(bo.items)\nprint(ana.items is bo.items)\n```\n\n```output\n['map']\nTrue\n```\n\nAna packed a map, and it turned up in Bo's backpack, because both backpacks refer to the one list that was made for the default.\n\nThe fix is to use `None` as the default, and create a new list inside the method when the caller didn't give one:\n\n```python\nclass Backpack:\n    def __init__(self, owner, items=None):\n        self.owner = owner\n        if items is None:\n            self.items = []\n        else:\n            self.items = items\n\n    def pack(self, item):\n        self.items.append(item)\n\n\nana = Backpack(\"Ana\")\nbo = Backpack(\"Bo\")\nana.pack(\"map\")\nprint(bo.items)\nprint(Backpack(\"Cy\", [\"tent\"]).items)\n```\n\n```output\n[]\n['tent']\n```\n\nNow `self.items = []` runs on every call, so every backpack gets a list of its own. Never use a list (or a dictionary) as a default value; use `None` and create it in the method.\n","task":"A meeting lasts 60 minutes unless told otherwise. Fill in the blank so that `minutes` has the default value 60, and the program prints:\n\n```\nStandup: 15 min\nPlanning: 60 min\n```\n","kind":"fill","seed":"class Meeting:\n    def __init__(self, title, [[minutes=60‖minutes = 60]]):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title}: {self.minutes} min\"\n\n\nprint(Meeting(\"Standup\", 15))\nprint(Meeting(\"Planning\"))\n","solution":"class Meeting:\n    def __init__(self, title, minutes=60):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title}: {self.minutes} min\"\n\n\nprint(Meeting(\"Standup\", 15))\nprint(Meeting(\"Planning\"))\n","hints":["The blank is the second parameter, `minutes`, written together with its default value.","A default value is written with `=` after the parameter's name: `minutes=60`."],"tests":[{"name":"Output","stdin":"","expect":"Standup: 15 min\nPlanning: 60 min","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Two teams are created without any players, and then a player joins one of them, but she shows up in both teams. Fix the class so that every team gets a list of its own, while `Team(\"Greens\", [\"Kim\"])` still starts with the players given. The program should print:\n\n```\nReds: Ada\nBlues:\nGreens: Kim, Lou\n```\n","kind":"code","seed":"class Team:\n    def __init__(self, name, players=[]):\n        self.name = name\n        self.players = players\n\n    def join(self, player):\n        self.players.append(player)\n\n    def __str__(self):\n        return f\"{self.name}: {', '.join(self.players)}\"\n\n\nreds = Team(\"Reds\")\nblues = Team(\"Blues\")\ngreens = Team(\"Greens\", [\"Kim\"])\nreds.join(\"Ada\")\ngreens.join(\"Lou\")\nprint(reds)\nprint(blues)\nprint(greens)\n","solution":"class Team:\n    def __init__(self, name, players=None):\n        self.name = name\n        if players is None:\n            self.players = []\n        else:\n            self.players = players\n\n    def join(self, player):\n        self.players.append(player)\n\n    def __str__(self):\n        return f\"{self.name}: {', '.join(self.players)}\"\n\n\nreds = Team(\"Reds\")\nblues = Team(\"Blues\")\ngreens = Team(\"Greens\", [\"Kim\"])\nreds.join(\"Ada\")\ngreens.join(\"Lou\")\nprint(reds)\nprint(blues)\nprint(greens)\n","hints":["The default list `[]` is created only once, so every team without players of its own shares it.","Make the default `None` instead: `players=None`.","In `__init__`, if `players is None`, set `self.players = []`; otherwise use the list that was given."],"tests":[{"name":"Output","stdin":"","expect":"Reds: Ada\nBlues:\nGreens: Kim, Lou","hidden":false}],"require":[],"forbid":[{"pattern":"=\\s*\\[\\s*\\]\\s*[,)]","message":"Don't use a list as a default value: use `None` and create the list inside `__init__`."}]},{"task":"Write the class `Alarm`. Its constructor takes the hour, the minute (by default 0) and a label (by default `\"Alarm\"`), and keeps them in the attributes `hour`, `minute` and `label`. The method `snooze(self, minutes=5)` moves the alarm later by the given number of minutes; when the minutes reach 60, the hour goes up by one (and after hour 23 comes hour 0). `__str__` returns a string such as `Wake up at 7:05`, with the minutes always written with two digits.\n\nFor example, this code:\n\n```python\nearly = Alarm(7, label=\"Wake up\")\nprint(early)\nearly.snooze()\nprint(early)\nlate = Alarm(23, 50)\nlate.snooze(15)\nprint(late)\n```\n\nprints:\n\n```\nWake up at 7:00\nWake up at 7:05\nAlarm at 0:05\n```\n","kind":"code","seed":"class Alarm:\n    pass\n\n\nif __name__ == \"__main__\":\n    early = Alarm(7, label=\"Wake up\")\n    print(early)\n    early.snooze()\n    print(early)\n    late = Alarm(23, 50)\n    late.snooze(15)\n    print(late)\n","solution":"class Alarm:\n    def __init__(self, hour, minute=0, label=\"Alarm\"):\n        self.hour = hour\n        self.minute = minute\n        self.label = label\n\n    def snooze(self, minutes=5):\n        total = self.hour * 60 + self.minute + minutes\n        self.hour = total // 60 % 24\n        self.minute = total % 60\n\n    def __str__(self):\n        return f\"{self.label} at {self.hour}:{self.minute:02}\"\n\n\nif __name__ == \"__main__\":\n    early = Alarm(7, label=\"Wake up\")\n    print(early)\n    early.snooze()\n    print(early)\n    late = Alarm(23, 50)\n    late.snooze(15)\n    print(late)\n","hints":["Give the defaults in the parameter list: `def __init__(self, hour, minute=0, label=\"Alarm\"):` and `def snooze(self, minutes=5):`.","In `snooze`, turn the time into minutes since midnight, add the snooze, then split it again: the hour is `total // 60 % 24` and the minute `total % 60`.","In `__str__`, `{self.minute:02}` writes the minutes with two digits."],"tests":[{"name":"Test 1","stdin":"","call":"early = Alarm(7, label=\"Wake up\")\nprint(early)\nearly.snooze()\nprint(early)\nlate = Alarm(23, 50)\nlate.snooze(15)\nprint(late)\n","expect":"Wake up at 7:00\nWake up at 7:05\nAlarm at 0:05","hidden":false},{"name":"Test 2","stdin":"","call":"nap = Alarm(14, 30)\nprint(nap)\nnap.snooze(30)\nprint(nap)\nnap.snooze()\nprint(nap.hour, nap.minute)\ngym = Alarm(6, 45, \"Gym\")\ngym.snooze(75)\nprint(gym)\n","expect":"Alarm at 14:30\nAlarm at 15:00\n15 5\nGym at 8:00","hidden":true}],"require":[],"forbid":[]}]}