{"id":"classes-attributes","slug":"attributes","title":"Attributes of every kind","text":"A constructor doesn't have to take every attribute as an argument. Some attributes have the same starting value in every new object: a new player has no points yet and no badges. The constructor sets those itself:\n\n```python\nclass Player:\n    def __init__(self, name):\n        self.name = name\n        self.points = 0\n        self.badges = []\n\n\nada = Player(\"Ada\")\nben = Player(\"Ben\")\n\nada.points += 15\nada.badges.append(\"first win\")\nben.points += 5\n\nprint(ada.name, ada.points, ada.badges)\nprint(ben.name, ben.points, ben.badges)\n```\n\n```output\nAda 15 ['first win']\nBen 5 []\n```\n\nA few things to notice here:\n\n- You can change an attribute at any time, from outside the class too, like any variable: `ada.points += 15`.\n- An attribute can hold any kind of value: a number, a string, a list, a dictionary, a `date`.\n- Every object gets a list of its own. Each time the constructor runs, `[]` makes a new empty list, so appending to Ada's badges doesn't touch Ben's.\n\n### A date as an attribute\n\nAn attribute can hold an object such as a `date`, and then you can use that object's own attributes and methods through it. Read `loan.borrowed.month` from left to right: the loan's `borrowed` date, and that date's `month`.\n\n```python\nfrom datetime import date\n\n\nclass Loan:\n    def __init__(self, title, borrowed):\n        self.title = title\n        self.borrowed = borrowed\n        self.returned = False\n\n\nloan = Loan(\"Dune\", date(2026, 9, 28))\nprint(loan.title, loan.borrowed, loan.returned)\nprint(loan.borrowed.month)\n\nloan.returned = True\nprint(loan.returned)\n```\n\n```output\nDune 2026-09-28 False\n9\nTrue\n```\n\n### Values worked out in the constructor\n\nThe constructor can also calculate an attribute from its arguments, and a parameter can have a default value, as in any function:\n\n```python\nclass Room:\n    def __init__(self, name, width, length, floor=1):\n        self.name = name\n        self.width = width\n        self.length = length\n        self.area = width * length\n        self.floor = floor\n\n\nkitchen = Room(\"kitchen\", 3, 4)\nattic = Room(\"attic\", 5, 6, 3)\n\nprint(kitchen.name, kitchen.area, kitchen.floor)\nprint(attic.name, attic.area, attic.floor)\n```\n\n```output\nkitchen 12 1\nattic 30 3\n```\n\nBe careful with calculated attributes: the area is worked out once, when the object is made. If the program later changes `kitchen.width`, `kitchen.area` keeps its old value. The next module shows a better way for values like this.\n","task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Plant:\n    def __init__(self, name):\n        self.name = name\n        self.height = 10\n        self.notes = []\n\n\nfern = Plant(\"fern\")\nivy = Plant(\"ivy\")\nfern.height += 5\nivy.notes.append(\"water\")\nivy.height = fern.height * 2\nprint(fern.height, fern.notes)\nprint(ivy.height, ivy.notes)\n","solution":"class Plant:\n    def __init__(self, name):\n        self.name = name\n        self.height = 10\n        self.notes = []\n\n\nfern = Plant(\"fern\")\nivy = Plant(\"ivy\")\nfern.height += 5\nivy.notes.append(\"water\")\nivy.height = fern.height * 2\nprint(fern.height, fern.notes)\nprint(ivy.height, ivy.notes)\n","hints":["Every new plant starts with the height 10 and an empty list of notes of its own.","The fern grows to 15, and the ivy's height is then set to twice the fern's.","Only the ivy's list gets a note."],"tests":[{"name":"Output","stdin":"","expect":"15 []\n30 ['water']","hidden":false}],"require":[],"forbid":[],"lines":["15 []","30 ['water']"],"more":[{"task":"Write a class `Recipe` whose constructor takes the recipe's name and the number of servings, and stores them in the attributes `name` and `servings`. When the servings aren't given, they are 2. Every new recipe also has the attribute `ingredients`, which starts as an empty list.\n\nThis code:\n\n```python\nsoup = Recipe(\"Tomato soup\", 4)\ntoast = Recipe(\"Cheese toast\")\nsoup.ingredients.append(\"tomatoes\")\nsoup.ingredients.append(\"onion\")\nprint(soup.name, soup.servings, soup.ingredients)\nprint(toast.name, toast.servings, toast.ingredients)\n```\n\nprints\n\n```\nTomato soup 4 ['tomatoes', 'onion']\nCheese toast 2 []\n```\n","kind":"code","seed":"# Write the class Recipe here.\n\n\nif __name__ == \"__main__\":\n    soup = Recipe(\"Tomato soup\", 4)\n    print(soup.name, soup.servings, soup.ingredients)\n","solution":"class Recipe:\n    def __init__(self, name, servings=2):\n        self.name = name\n        self.servings = servings\n        self.ingredients = []\n\n\nif __name__ == \"__main__\":\n    soup = Recipe(\"Tomato soup\", 4)\n    print(soup.name, soup.servings, soup.ingredients)\n","hints":["A default value goes in the parameter list: `def __init__(self, name, servings=2):`.","The ingredients aren't a parameter at all: the constructor sets `self.ingredients = []`.","Make the empty list inside the constructor, so that every recipe gets a list of its own."],"tests":[{"name":"Test 1","stdin":"","call":"soup = Recipe(\"Tomato soup\", 4)\ntoast = Recipe(\"Cheese toast\")\nsoup.ingredients.append(\"tomatoes\")\nsoup.ingredients.append(\"onion\")\nprint(soup.name, soup.servings, soup.ingredients)\nprint(toast.name, toast.servings, toast.ingredients)\n","expect":"Tomato soup 4 ['tomatoes', 'onion']\nCheese toast 2 []","hidden":false},{"name":"Test 2","stdin":"","call":"a = Recipe(\"Pancakes\", 6)\nb = Recipe(\"Pancakes\")\nb.ingredients.append(\"flour\")\nb.servings += 1\nprint(a.name, a.servings, a.ingredients)\nprint(b.name, b.servings, b.ingredients)\n","expect":"Pancakes 6 []\nPancakes 3 ['flour']","hidden":true}],"require":[],"forbid":[]},{"task":"Write a class `Trip` whose constructor takes the destination and the first and last day of the trip as `date` objects. It stores them in the attributes `destination`, `start` and `end`, and also sets these two:\n\n- `nights`: how many nights the trip has, which is the number of days between the start and the end.\n- `packed`: `False` for every new trip.\n\n(In part 7 you subtracted dates: the result has the attribute `days`.) This code:\n\n```python\ntrip = Trip(\"Lisbon\", date(2026, 6, 1), date(2026, 6, 8))\nprint(trip.destination, trip.nights, trip.packed)\ntrip.packed = True\nprint(trip.start, trip.end, trip.packed)\n```\n\nprints\n\n```\nLisbon 7 False\n2026-06-01 2026-06-08 True\n```\n","kind":"code","seed":"from datetime import date\n\n\n# Write the class Trip here.\n\n\nif __name__ == \"__main__\":\n    trip = Trip(\"Lisbon\", date(2026, 6, 1), date(2026, 6, 8))\n    print(trip.destination, trip.nights, trip.packed)\n","solution":"from datetime import date\n\n\nclass Trip:\n    def __init__(self, destination, start, end):\n        self.destination = destination\n        self.start = start\n        self.end = end\n        self.nights = (end - start).days\n        self.packed = False\n\n\nif __name__ == \"__main__\":\n    trip = Trip(\"Lisbon\", date(2026, 6, 1), date(2026, 6, 8))\n    print(trip.destination, trip.nights, trip.packed)\n","hints":["The constructor has the parameters `self, destination, start, end`, and stores the three arguments as attributes.","`end - start` is a time difference, and its attribute `days` is the number of nights: `self.nights = (end - start).days`.","`packed` isn't a parameter: set `self.packed = False` in the constructor."],"tests":[{"name":"Test 1","stdin":"","call":"trip = Trip(\"Lisbon\", date(2026, 6, 1), date(2026, 6, 8))\nprint(trip.destination, trip.nights, trip.packed)\ntrip.packed = True\nprint(trip.start, trip.end, trip.packed)\n","expect":"Lisbon 7 False\n2026-06-01 2026-06-08 True","hidden":false},{"name":"Test 2","stdin":"","call":"long = Trip(\"Hokkaido\", date(2026, 12, 20), date(2027, 1, 6))\nshort = Trip(\"Tallinn\", date(2026, 5, 9), date(2026, 5, 10))\nprint(long.destination, long.nights, long.packed)\nprint(short.destination, short.nights, short.start.month)\n","expect":"Hokkaido 17 False\nTallinn 1 5","hidden":true}],"require":[],"forbid":[]}]}