Learning Goals
3 minBy the end of this lesson you can:
- Build a list of objects, in one go or one
appendat a time. - Loop over the list to print, total, count and filter.
- Write a search that returns the object found, or
None. - Sort a list of objects with a
key=function.
Warm-Up · Two Lines and a Crash
5 minHow many lines print before this stops, and what does the error say?
class MenuItem: """One dish on the canteen menu.""" def __init__(self, name, price): self.name = name self.price = price menu = [MenuItem("Pizza", 4.50), MenuItem("Hot chocolate", 2.50), MenuItem] for item in menu: print(item.name)
Answer
Output
Pizza Hot chocolate Traceback (most recent call last): AttributeError: type object 'MenuItem' has no attribute 'name'
The third item is the blueprint, not a dish. It slipped into the list because a class is a perfectly ordinary value in Python — you can store it anywhere. Only the brackets make an object.
type object 'MenuItem' is Python telling you it is looking at the class itself. When you see that, hunt for a missing ().
New Concept · A List Full of Objects
12 minA list holds anything, so it holds your objects. Build it in one go:
menu = [ MenuItem("Pizza", 4.50), MenuItem("Pancake", 1.50), MenuItem("Hot chocolate", 2.50), ]
Or grow it one at a time — which is what you do when the data comes from the user:
menu = [] menu.append(MenuItem("Fried rice", 8.00)) menu.append(MenuItem("Ice cream", 3.50))
Printing the whole list is now one loop, and it is where __str__ pays you back:
for item in menu: print(item)
Without __str__ you would get three lines of <__main__.MenuItem object at 0x...>. With it, you get your own tidy lines.
item is the object, item.price is a numberThis is the one confusion to stamp out today. The loop variable is the whole dish; the dot gets you one fact about it. Mixing them up is behind almost every bug in this lesson.
Totalling and counting are the same accumulator patterns you already use, with a dot added:
total = 0 cheap_count = 0 for item in menu: total = total + item.price if item.price < 3: cheap_count = cheap_count + 1 print(f"Whole menu: $ {total:.2f}") print("Dishes under $3:", cheap_count)
Filtering builds a smaller list of the same objects — not copies, the very same ones:
snacks = [] for item in menu: if item.price < 3: snacks.append(item)
Searching is the pattern you will use most, and the trick is what it gives back:
def find_item(menu, wanted): """Return the dish with that name, or None if it is not on the menu.""" for item in menu: if item.name.lower() == wanted.lower(): return item return None
It returns the object, so the caller can immediately use it — read its attributes, call its methods, change it. Always check the miss:
found = find_item(menu, "hot chocolate") if found is None: print("Not on the menu today.") else: print(found.name, "costs $", found.price)
Handing back 2 means every caller must remember which list it came from and write menu[2].price. Handing back the dish means they just use it. None is the honest answer for "there isn't one", and is None is how you test for it.
Sorting needs one small function that says which fact to sort on — exactly the key= idea from lists, now reading an attribute:
def price_of(item): """Key function: sort a menu by price.""" return item.price for item in sorted(menu, key=price_of): print(item)
sorted calls price_of once per dish and orders by what comes back. Swap in name_of and the same menu sorts alphabetically.
Worked Example · The Canteen Menu
12 minOne class, one list, and five things done to it. Type it out and run it.
class MenuItem: """One dish on the canteen menu, with today's stock.""" def __init__(self, name, price, stock=0): self.name = name self.price = price self.stock = stock def sell(self, qty=1): """Sell qty portions. Return True if there were enough.""" if qty > self.stock: return False self.stock = self.stock - qty return True def value(self): """Return the money this dish's remaining stock is worth.""" return self.price * self.stock def __str__(self): return f"{self.name:<16} $ {self.price:>5.2f} stock {self.stock}" def price_of(item): """Key function: sort a menu by price.""" return item.price def find_item(menu, wanted): """Return the dish with that name, or None.""" for item in menu: if item.name.lower() == wanted.lower(): return item return None menu = [ MenuItem("Pizza", 4.50, 20), MenuItem("Pancake", 1.50, 35), MenuItem("Fried rice", 8.00, 12), MenuItem("Hot chocolate", 2.50, 40), ] print("TODAY'S MENU") for item in menu: print(item) total = 0 for item in menu: total = total + item.value() print(f"Stock on hand: $ {total:.2f}") wanted = "pizza" dish = find_item(menu, wanted) if dish is None: print(wanted, "is not on the menu.") elif dish.sell(3): print("Sold 3 x", dish.name, "- stock now", dish.stock) else: print("Not enough", dish.name, "left.") print("CHEAPEST FIRST") for item in sorted(menu, key=price_of): print(item)
Output
TODAY'S MENU Pizza $ 4.50 stock 20 Pancake $ 1.50 stock 35 Fried rice $ 8.00 stock 12 Hot chocolate $ 2.50 stock 40 Stock on hand: $338.50 Sold 3 x Pizza - stock now 17 CHEAPEST FIRST Pancake $ 1.50 stock 35 Hot chocolate $ 2.50 stock 40 Pizza $ 4.50 stock 17 Fried rice $ 8.00 stock 12
find_item did not hand back a copy. dish and menu[0] are the same object, so selling three portions is visible everywhere — including in the sorted listing printed afterwards. That is usually exactly what you want, and it is worth knowing it is happening.
Two experiments. Ask for "soup" and watch the None branch run. Then try to sell 99 pizza, and notice the refusal comes from the class, not the program.
Try It Yourself
13 minTake the Player class from OOP-03. Build a list of four players, damage two of them by different amounts, then loop once to print every player and count how many are still alive.
Hint
party = [Player("Felix"), Player("Ana"), Player("Leo")] alive = 0 for player in party: print(player) if player.is_alive(): alive = alive + 1
Add three functions beside your party list:
find_player(party, name)— returns the player object, orNone.hp_of(player)— a key function, so you can print the party weakest-first.weakest(party)— returns the player object with the lowest hp, using a loop rather thansorted.
Then use find_player to heal one player by name, and print the party again. If the name is not in the party, say so and do not crash.
Mini-Challenge 🔥 · Debug: Three Identical Students
8 minNia's program asks for three pupils, then crashes when it looks for the top mark. Find three mistakes.
class Student:
"""One pupil and one mark."""
def __init__(self, name, mark):
self.name = name
self.mark = mark
students = []
for i in range(3):
name = input("Name: ")
mark = int(input("Mark: "))
students.append(Student)
top = students[0]
for student in students:
if student.mark > top:
top = student.mark
print("Top pupil:", top.name, "with", top.mark)Answer
students.append(Student)stores the blueprint. Three times over, so the list holds the same class three times and the typed names are thrown away. It must bestudents.append(Student(name, mark)).student.mark > topcompares a number with a student. You are asking "is 78 bigger than Mia?", which Python cannot answer:TypeError. It must bestudent.mark > top.mark.top = student.markthrows the pupil away. After one roundtopis a number, so the last line asks a number for its.name. Keep the object:top = student.
Two of the three are the same misunderstanding wearing different clothes: the object and one attribute of the object are not interchangeable. The fixed loop:
top = students[0] for student in students: if student.mark > top.mark: top = student
Recap
3 minObjects go in lists like anything else, and every loop you know then works on them: printing (which calls __str__), totalling, counting, filtering. A search should return the object itself, or None when there is no match — test that with is None. Sorting takes a small key function that reads one attribute. Throughout, keep the object and one of its attributes straight in your head: item is the dish, item.price is a number.
Vocabulary Card
- list of objects
- The normal way to hold many things of the same class.
- key function
- A one-line function passed to
sorted(..., key=...)that returns the value to sort on. - None
- Python's "nothing here" value, returned by a search that found no match.
- reference
- A found object is the same object as the one in the list — changing it changes both.
Homework
4 minTake your own class and build a list of at least five objects. Then write a small report program that:
- prints every object on its own line, using
__str__; - prints one total or average worked out from an attribute;
- finds one object by name and prints it — handling the miss without crashing;
- prints the whole list again, sorted by a key function.
Hint: write the search as a function that returns the object. If you find yourself returning an index, you are making work for later.
Sample · rak_buku.py
# rak_buku.py - one shelf, five books, four questions class Book: """One book on my shelf.""" def __init__(self, title, author, price, pages): self.title = title self.author = author self.price = price self.pages = pages def __str__(self): return f"{self.title:<24} {self.author:<14} $ {self.price:>6.2f} {self.pages}p" def find_book(shelf, wanted): """Return the book with that title, or None.""" for book in shelf: if book.title.lower() == wanted.lower(): return book return None def pages_of(book): """Key function: sort a shelf by length.""" return book.pages shelf = [ Book("Sejarah Tingkatan 2", "KPM", 12.90, 210), Book("Lat: Village Boy", "Lat", 24.00, 144), Book("Python for Kids", "Briggs", 79.90, 344), Book("Bulan Kertas", "Ella", 18.50, 96), Book("The Rice Mother", "Manicka", 42.00, 448), ] for book in shelf: print(book) total = 0 for book in shelf: total = total + book.price print(f"Shelf value: $ {total:.2f} (average $ {total / len(shelf):.2f})") found = find_book(shelf, "lat: village boy") if found is None: print("Not on this shelf.") else: print("Found:", found) print("SHORTEST FIRST") for book in sorted(shelf, key=pages_of): print(book)
Notice the shape: the class knows how to describe one book, and the three little functions ask questions about many. Your class and attributes will differ — the four jobs are what to copy.