Learning Goals
3 minBy the end of this lesson you can:
- Build a list-of-dicts dataset and access a single field like
students[0]["name"]. - Loop the whole dataset and filter records by an
ifon one of their fields. - Sum, average and find the best record across many rows.
- Add a new record with
.append({ ... }).
Warm-Up
5 minIn Level 1 you tracked students with parallel lists — one for names, one for ages, one for scores. It worked, but it was fragile: if you sorted one list and forgot to sort the others, the data fell out of sync.
Today's shape solves that. Each row becomes one self-contained dictionary, and the rows live in a list. Predict what this prints:
students = [ {"name": "Aisyah", "age": 12, "score": 92}, {"name": "Wei Jie", "age": 13, "score": 87}, {"name": "Priya", "age": 12, "score": 95}, ] print(students[0]) print(students[0]["name"]) print(students[-1]["score"])
Show the answer
{'name': 'Aisyah', 'age': 12, 'score': 92}
Aisyah
95Two indexings stacked: students[0] picks the first record, then ["name"] picks the field. The brackets read left to right — "row 0, column name".
One list, many dicts. Each dict has the same keys. That single shape is what every spreadsheet column header, every JSON file and every database row already looks like.
New Concept · Working with Records
14 minThe shape
A "record" is a dictionary. The dataset is a list of records. Every record has the same keys.
students = [ {"name": "Aisyah", "age": 12, "score": 92}, {"name": "Wei Jie", "age": 13, "score": 87}, {"name": "Priya", "age": 12, "score": 95}, {"name": "Iman", "age": 13, "score": 70}, ]
One field at a time
To get one cell of one row: index the list, then key the dict. Two brackets, in that order.
first = students[0] # the whole record print(first["score"]) # → 92 # In one line: print(students[2]["name"]) # → Priya
Looping every record
A normal for loop hands you one dictionary at a time. Inside the loop, you access fields by key.
for s in students: print(s["name"], ":", s["score"])
Aisyah : 92 Wei Jie : 87 Priya : 95 Iman : 70
Filtering by a field
To pull only the rows that match a condition, loop and use if:
for s in students: if s["score"] >= 90: print(s["name"], "got A:", s["score"])
Aisyah got A: 92 Priya got A: 95
Summing or averaging
The standard add-as-you-go pattern. Either with a running total, or with sum() over a list comprehension.
# Running total total = 0 for s in students: total += s["score"] print("Total:", total) print("Avg :", total / len(students)) # One-liner using a comprehension (from PY-L2-02) print("Avg :", sum([s["score"] for s in students]) / len(students))
Finding the best record
Scan for the maximum, but track the whole record, not just the number — because we want to know who:
best = students[0] for s in students: if s["score"] > best["score"]: best = s print("Top student:", best["name"], "(", best["score"], ")")
Notice the "keep the whole row" trick: best is a dict, not a number. That's what lets us print both name and score at the end.
Adding a new record
Build a fresh dictionary, then .append it to the list:
students.append({"name": "Aizat", "age": 12, "score": 81}) print(len(students))
Updating a record in place
Records you get from a loop are references, not copies. Edit the dict in the loop and the list updates:
for s in students: if s["name"] == "Wei Jie": s["score"] = 90 # ← changes the real record print(students[1]) # → {'name': 'Wei Jie', 'age': 13, 'score': 90}
This pattern shows up everywhere. Products in a shop. Songs in a playlist. Tweets in a feed. Pokémon in a Pokédex. Once you can loop and filter a students list, you can do the same thing to any of them — only the field names change.
Worked Example · The Mini Pokédex
12 minThe story
You're building a tiny Pokédex — six creatures, each with a name, a type, and an attack power. Then five questions you might ask of the dataset, each answered by one of the patterns from the concept section.
Save as pokedex.py:
Code
# pokedex.py — list of dicts, six common patterns pokedex = [ {"name": "Bulbasaur", "type": "grass", "atk": 49}, {"name": "Charmander", "type": "fire", "atk": 52}, {"name": "Squirtle", "type": "water", "atk": 48}, {"name": "Pikachu", "type": "electric", "atk": 55}, {"name": "Geodude", "type": "rock", "atk": 80}, {"name": "Eevee", "type": "normal", "atk": 55}, ] # 1 — print the whole pokedex print("Pokédex:") for p in pokedex: print(" -", p["name"], "(", p["type"], ", atk", p["atk"], ")") # 2 — filter: every fire-type print() print("Fire types:") for p in pokedex: if p["type"] == "fire": print(" -", p["name"]) # 3 — average attack across the whole dex total_atk = sum([p["atk"] for p in pokedex]) print() print("Average atk:", total_atk / len(pokedex)) # 4 — strongest pokémon (whole record) strongest = pokedex[0] for p in pokedex: if p["atk"] > strongest["atk"]: strongest = p print("Strongest :", strongest["name"], "(atk", strongest["atk"], ")") # 5 — add a new pokémon pokedex.append({"name": "Onix", "type": "rock", "atk": 45}) print("After Onix :", len(pokedex), "entries") # 6 — update Pikachu's attack for p in pokedex: if p["name"] == "Pikachu": p["atk"] = 60 print("Pikachu now:", pokedex[3])
Output
Pokédex:
- Bulbasaur ( grass , atk 49 )
- Charmander ( fire , atk 52 )
- Squirtle ( water , atk 48 )
- Pikachu ( electric , atk 55 )
- Geodude ( rock , atk 80 )
- Eevee ( normal , atk 55 )
Fire types:
- Charmander
Average atk: 56.5
Strongest : Geodude (atk 80 )
After Onix : 7 entries
Pikachu now: {'name': 'Pikachu', 'type': 'electric', 'atk': 60}Read the diff
Six different questions, six tiny loops — each one with the same shape: for p in pokedex:, then p["field"] inside. Once that shape is in your fingers, every "list of records" question feels familiar.
Try It Yourself
13 minUse this dataset for all three exercises:
shop = [ {"item": "pencil", "qty": 50, "price": 0.50}, {"item": "eraser", "qty": 30, "price": 0.80}, {"item": "ruler", "qty": 12, "price": 1.20}, {"item": "sharpener", "qty": 8, "price": 1.50}, {"item": "book", "qty": 20, "price": 3.00}, ]
Print one line per item: pencil x 50 @ RM 0.5.
Hint
for s in shop: print(s["item"], "x", s["qty"], "@ RM", s["price"])
Print only the items with qty below 15. Add the line STOCK ALERT at the top if anything qualifies.
Hint
low = [s for s in shop if s["qty"] < 15] if len(low) > 0: print("STOCK ALERT") for s in low: print(" -", s["item"], "(", s["qty"], "left )")
This combines a list comprehension (the filter) with a normal loop (the report). The comprehension is from PY-L2-02; the filter is just an if at the end.
Print the total value of the shop's stock — multiply each item's qty by its price and add them all up. Then print the most expensive item by unit price.
Hint
# Total value total = 0 for s in shop: total = total + s["qty"] * s["price"] print("Stock value: RM", total) # Most expensive top = shop[0] for s in shop: if s["price"] > top["price"]: top = s print("Priciest :", top["item"], "@ RM", top["price"])
The "most expensive" pattern is the same scan as PY-L1-22 — start with the first row, replace it whenever you see a bigger one. Tracking the whole dict means you can print the name at the end.
Mini-Challenge · The Class Mark-Sheet
8 minBuild marksheet.py. You'll be given six students with three subject marks each, and asked to compute a per-student total, sort the class, and award the top score.
class_data = [ {"name": "Aisyah", "maths": 88, "english": 92, "science": 85}, {"name": "Wei Jie", "maths": 74, "english": 68, "science": 91}, {"name": "Priya", "maths": 95, "english": 97, "science": 96}, {"name": "Iman", "maths": 55, "english": 60, "science": 58}, {"name": "Aizat", "maths": 82, "english": 78, "science": 80}, {"name": "Hafiz", "maths": 65, "english": 72, "science": 70}, ]
Your file must:
- Loop the list and add a new key
"total"to each record — the sum of the three subject scores. - Print every student as
Aisyah: maths 88, english 92, science 85 = 265. - Print the class average for each subject — three numbers — using
sum([s[subject] for s in class_data]) / len(class_data). - Find and print the top student by total — scan and track the whole dict.
Stretch goal. Sort the class by "total" descending using sorted(class_data, key=lambda s: s["total"], reverse=True) and print the leaderboard 1..6.
Show one possible solution
# marksheet.py — list-of-dicts class report class_data = [ {"name": "Aisyah", "maths": 88, "english": 92, "science": 85}, {"name": "Wei Jie", "maths": 74, "english": 68, "science": 91}, {"name": "Priya", "maths": 95, "english": 97, "science": 96}, {"name": "Iman", "maths": 55, "english": 60, "science": 58}, {"name": "Aizat", "maths": 82, "english": 78, "science": 80}, {"name": "Hafiz", "maths": 65, "english": 72, "science": 70}, ] # Step 1 — add a "total" field to every record for s in class_data: s["total"] = s["maths"] + s["english"] + s["science"] # Step 2 — print everyone for s in class_data: print(s["name"], ": maths", s["maths"], ", english", s["english"], ", science", s["science"], "=", s["total"]) # Step 3 — subject averages for subject in ("maths", "english", "science"): avg = sum([s[subject] for s in class_data]) / len(class_data) print("Avg", subject, ":", avg) # Step 4 — top student top = class_data[0] for s in class_data: if s["total"] > top["total"]: top = s print("Top student:", top["name"], "(", top["total"], ")") # Stretch — leaderboard ranked = sorted(class_data, key=lambda s: s["total"], reverse=True) print() print("Leaderboard") for rank, s in enumerate(ranked, start=1): print(rank, ".", s["name"], "—", s["total"])
Non-negotiables: a new "total" key added to every record, a loop that prints one line per student, three subject averages, and a top-student scan. The sorted(..., key=lambda s: s["total"]) recipe is the same as PY-L2-04's — treat it as a stamp you copy and adjust.
Recap
3 minA list of dictionaries is the shape every real dataset takes — students, products, orders, songs, pokémon. Loop with for record in data:, then access fields with record["field"]. Filter with an if, sum with a comprehension, scan for the best by tracking the whole dict, and add new records with .append({...}). The same six patterns answer almost every question you'll be asked of a list-of-records.
Vocabulary Card
- record
- A dictionary that holds one row of a dataset.
- field
- A key inside a record — the equivalent of a column in a spreadsheet.
- list of dicts
- The standard shape:
[{...}, {...}, {...}]. Every record has the same keys. - data[i][key]
- Two indexings — pick the record, then pick the field.
Homework
4 minBuild library_records.py. Each library book is a record with title, author, copies and loaned (how many of the copies are currently checked out).
Your file must:
- Start with at least five book records of your choice.
- Loop the list and print one line per book:
Title — Author — 4/5 available(the available count iscopies - loaned). - Print the total number of books across the library (sum of
copies) and the total currently loaned out (sum ofloaned). - Print all books that have at least one available copy (filter by
loaned < copies). - Append a new book by your favourite author and re-print the count.
- Update the
loanedcount of one book — a new check-out — and re-print just that book.
Stretch. Use sorted(books, key=lambda b: b["copies"] - b["loaned"], reverse=True) to print the books with the most available copies first.
Sample · library_records.py
# library_records.py — list of dicts, library style books = [ {"title": "Harry Potter", "author": "Rowling", "copies": 5, "loaned": 3}, {"title": "Wings of Fire", "author": "Sutherland","copies": 7, "loaned": 2}, {"title": "Percy Jackson", "author": "Riordan", "copies": 4, "loaned": 4}, {"title": "Charlotte's Web", "author": "White", "copies": 3, "loaned": 1}, {"title": "The Hobbit", "author": "Tolkien", "copies": 2, "loaned": 0}, ] for b in books: available = b["copies"] - b["loaned"] print(b["title"], "—", b["author"], "—", available, "/", b["copies"], "available") print() print("Total copies :", sum([b["copies"] for b in books])) print("Total loaned :", sum([b["loaned"] for b in books])) print() print("Available books:") for b in books: if b["loaned"] < b["copies"]: print(" -", b["title"]) # New book books.append({"title": "Tom Gates", "author": "Pichon", "copies": 6, "loaned": 0}) print() print("After new book:", len(books), "titles.") # New loan for b in books: if b["title"] == "The Hobbit": b["loaned"] = b["loaned"] + 1 print("Updated:", b) # Stretch — most available first ranked = sorted(books, key=lambda b: b["copies"] - b["loaned"], reverse=True) print() print("Most-available first:") for b in ranked: print(" -", b["title"], ":", b["copies"] - b["loaned"], "available")
Non-negotiables: at least 5 records, a loop with copies - loaned, two sum([...]) totals, one .append, and one in-place update. Your choice of books is up to you.