{"id":"object-references-lists","slug":"lists-of-objects","title":"Objects in lists and dictionaries","text":"An object of a class you wrote can go anywhere a number or a string can: into a variable, a list, a dictionary or a function call. To Python, your classes are types just like the ones it has built in. `5` is an `int`, `\"hi\"` is a `str`, `Song(\"Slow River\", 302)` is a `Song`, and all of them are **objects**. You have already kept your own objects in variables and lists and given them to functions. This part of the course looks at what really happens when you do, starting with lists and dictionaries of objects.\n\n### A list of objects\n\nHere a playlist is a list of `Song` objects. The loop takes the songs one at a time, and `song.title` reaches the title of the song the loop is at:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n\nplaylist = []\nplaylist.append(Song(\"Morning Light\", 215))\nplaylist.append(Song(\"Slow River\", 302))\nplaylist.append(Song(\"Neon Rain\", 187))\n\ntotal = 0\nfor song in playlist:\n    print(song.title)\n    total += song.seconds\n\nprint(\"Total length:\", total, \"seconds\")\n```\n\n```output\nMorning Light\nSlow River\nNeon Rain\nTotal length: 704 seconds\n```\n\nEach `Song(...)` call creates a new object, and `append` puts it at the end of the list. In the loop, `song` is a `Song` object, so everything a song has is there behind the dot.\n\n### Finding the right object\n\nA function can look through a list of objects and return one of them. Returning the whole object, not just its title, lets the caller use whatever it needs from it:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n    def __str__(self):\n        return f\"{self.title} ({self.seconds} s)\"\n\n\ndef longest(songs):\n    best = songs[0]\n    for song in songs:\n        if song.seconds > best.seconds:\n            best = song\n    return best\n\n\nplaylist = [Song(\"Morning Light\", 215), Song(\"Slow River\", 302), Song(\"Neon Rain\", 187)]\nwinner = longest(playlist)\nprint(winner)\nprint(\"Title only:\", winner.title)\n```\n\n```output\nSlow River (302 s)\nTitle only: Slow River\n```\n\n`best` starts as the first song and is replaced whenever a longer one comes along. The comparison is between `song.seconds` and `best.seconds`, two numbers. Comparing the songs themselves doesn't work, because Python has no idea what would make one song \"greater\" than another:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n\nfirst = Song(\"Morning Light\", 215)\nsecond = Song(\"Slow River\", 302)\nprint(first > second)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 9, in <module>\n    print(first > second)\n          ^^^^^^^^^^^^^^\nTypeError: '>' not supported between instances of 'Song' and 'Song'\n```\n\nWhen you see this error, decide which attribute you meant to compare, and compare that.\n\n### Objects in a dictionary\n\nObjects can be the values of a dictionary too. Here each song has a code, as on a jukebox:\n\n```python\nclass Song:\n    def __init__(self, title, seconds):\n        self.title = title\n        self.seconds = seconds\n\n\njukebox = {}\njukebox[\"A1\"] = Song(\"Morning Light\", 215)\njukebox[\"B7\"] = Song(\"Slow River\", 302)\n\ncode = \"B7\"\nprint(\"Now playing:\", jukebox[code].title)\n```\n\n```output\nNow playing: Slow River\n```\n\n`jukebox[code]` is a `Song` object, so `.title` can come right after it.\n","task":"The program should print the total running time of the movies in the list. Fill in the blanks so it prints:\n\n```\nTotal: 371 minutes\n```\n","kind":"fill","seed":"class Movie:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n\nmovies = [Movie(\"Space Cats\", 98), Movie(\"The Long Road\", 151), Movie(\"Tiny Giants\", 122)]\n\ntotal = 0\nfor movie in [[movies]]:\n    total += [[movie.minutes]]\n\nprint(f\"Total: {total} minutes\")\n","solution":"class Movie:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n\nmovies = [Movie(\"Space Cats\", 98), Movie(\"The Long Road\", 151), Movie(\"Tiny Giants\", 122)]\n\ntotal = 0\nfor movie in movies:\n    total += movie.minutes\n\nprint(f\"Total: {total} minutes\")\n","hints":["The loop should go through the list of movies. What is the list called?","Each round, `movie` is one `Movie` object. Its length is in its attribute `minutes`, which you reach with a dot: `movie.minutes`."],"tests":[{"name":"Output","stdin":"","expect":"Total: 371 minutes","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write a function `cheapest(products)`. It gets a list of `Product` objects and returns the cheapest product: the object itself, not its name. You can assume the list has at least one product and that no two products cost the same.\n\nFor example, this code:\n\n```python\nitems = [Product(\"Tea\", 3.5), Product(\"Bread\", 2.25), Product(\"Cheese\", 6.0)]\nprint(cheapest(items))\n```\n\nprints:\n\n```\nBread (2.25)\n```\n","kind":"code","seed":"class Product:\n    def __init__(self, name, price):\n        self.name = name\n        self.price = price\n\n    def __str__(self):\n        return f\"{self.name} ({self.price})\"\n\n\ndef cheapest(products):\n    pass\n\n\nif __name__ == \"__main__\":\n    items = [Product(\"Tea\", 3.5), Product(\"Bread\", 2.25), Product(\"Cheese\", 6.0)]\n    print(cheapest(items))\n","solution":"class Product:\n    def __init__(self, name, price):\n        self.name = name\n        self.price = price\n\n    def __str__(self):\n        return f\"{self.name} ({self.price})\"\n\n\ndef cheapest(products):\n    best = products[0]\n    for product in products:\n        if product.price < best.price:\n            best = product\n    return best\n\n\nif __name__ == \"__main__\":\n    items = [Product(\"Tea\", 3.5), Product(\"Bread\", 2.25), Product(\"Cheese\", 6.0)]\n    print(cheapest(items))\n","hints":["Start with the first product as the cheapest so far, then go through the list and compare each product with it.","Compare the prices, not the products: `if product.price < best.price:`.","After the loop, return the object you kept (`best`), not `best.name`."],"tests":[{"name":"Test 1","stdin":"","call":"items = [Product(\"Tea\", 3.5), Product(\"Bread\", 2.25), Product(\"Cheese\", 6.0)]\nprint(cheapest(items))\n","expect":"Bread (2.25)","hidden":false},{"name":"print(cheapest([Product(\"Salt\", 0.8), Product(\"Rice\", 1.9)]))","stdin":"","call":"print(cheapest([Product(\"Salt\", 0.8), Product(\"Rice\", 1.9)]))\n","expect":"Salt (0.8)","hidden":true},{"name":"Test 3","stdin":"","call":"shelf = [Product(\"Jam\", 4.1), Product(\"Honey\", 7.5), Product(\"Oats\", 1.2)]\nfound = cheapest(shelf)\nprint(found.name, found.price)\n","expect":"Oats 1.2","hidden":true},{"name":"print(cheapest([Product(\"Milk\", 1.15)]))","stdin":"","call":"print(cheapest([Product(\"Milk\", 1.15)]))\n","expect":"Milk (1.15)","hidden":true}],"require":[],"forbid":[]},{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"class Pet:\n    def __init__(self, name, age):\n        self.name = name\n        self.age = age\n\n\npets = {\"cat\": Pet(\"Miso\", 3), \"dog\": Pet(\"Rex\", 7)}\npets[\"cat\"].age += 1\nfor kind in pets:\n    print(kind, pets[kind].name, pets[kind].age)\n","solution":"class Pet:\n    def __init__(self, name, age):\n        self.name = name\n        self.age = age\n\n\npets = {\"cat\": Pet(\"Miso\", 3), \"dog\": Pet(\"Rex\", 7)}\npets[\"cat\"].age += 1\nfor kind in pets:\n    print(kind, pets[kind].name, pets[kind].age)\n","hints":["The dictionary's values are `Pet` objects, so `pets[\"cat\"]` is the pet Miso.","`pets[\"cat\"].age += 1` changes Miso's age from 3 to 4. Rex isn't touched.","The loop goes through the keys in the order they were added, and prints the key, the name and the age on one line."],"tests":[{"name":"Output","stdin":"","expect":"cat Miso 4\ndog Rex 7","hidden":false}],"require":[],"forbid":[],"lines":["cat Miso 4","dog Rex 7"]}]}