Learning Goals
3 minBy the end of this lesson you can:
- Build a menu loop that keeps running until the user chooses to quit.
- Implement create, read, update and delete over one list of records.
- Validate a menu choice and a record number so nothing crashes.
Warm-Up · Name the Four
5 minYou have already written all four operations in this section. Match each to the tool you used.
Answer
- Create —
append()for the end,insert()for a position. (LST-03) - Read — indexing, slicing,
in,index(), and a loop to show them all. (LST-01, LST-04) - Update —
items[i] = value, or editing one field of a record. (LST-02) - Delete —
pop(),remove()ordel. (LST-03)
Nothing new today. The work is putting them behind a menu and making them safe.
Not the list operations — the input. Every menu choice and record number is typed by a person, and any of them can be a word, a blank, or a number that is out of range.
New Concept · The Menu Loop
12 minA stall that stays open. Show the menu, take one order, deal with it, show the menu again — until someone says "that is all".
tasks = [] while True: print() print("1 Add 2 View 3 Quit") choice = input("Choose: ").strip() if choice == "1": tasks.append(input("Task: ").strip()) elif choice == "2": print(tasks) elif choice == "3": print("Bye!") break else: print("Please choose 1, 2 or 3.")
Three parts, and every menu program has them:
while True— loop forever, on purpose.break— the only way out, reached by one deliberate choice.- A final
else— so a typo says something useful instead of silently doing nothing.
Records, not parallel lists
Lesson 5 showed why: sort two parallel lists separately and they stop matching. Keep each record as one small list.
# Each task is [description, done] tasks = [ ["Finish homework", False], ["Tidy room", True], ] for description, done in tasks: mark = "x" if done else " " print(f"[{mark}] {description}")
Output
[ ] Finish homework [x] Tidy room
Asking for a record number safely
The user sees 1, 2, 3; the list uses 0, 1, 2. Both the conversion and the range need checking.
def ask_index(prompt, items): """Ask for a record number, return its index, or -1 if unusable.""" if not items: print("Nothing in the list yet.") return -1 raw = input(prompt).strip() if not raw.isdigit(): print("Please type a number.") return -1 number = int(raw) if number < 1 or number > len(items): print(f"Choose between 1 and {len(items)}.") return -1 return number - 1
Three guards in nine lines: empty list, not a number, out of range. Every one of them is a crash you have already met in this section.
-1 for "no"Lesson 4 warned that -1 is a real index. It is safe here only because the caller tests if i == -1 and stops. Section 7 · Errors & Debugging shows the cleaner way.
Build It · manager.py, Four Steps
12 minType it in four goes, running after each. Do not paste the whole thing.
Step 1 — the data and the view
# manager.py - add, view, edit and delete a list of tasks tasks = [ ["Finish Sejarah homework", False], ["Practise piano", True], ["Buy gift for Ella", False], ] def show_all(): """Read - print every task with its number.""" print() if not tasks: print(" (nothing yet)") return print(f" {'#':<4}{'Done':<6}Task") for i in range(len(tasks)): description, done = tasks[i] mark = "yes" if done else "no" print(f" {i + 1:<4}{mark:<6}{description}") outstanding = 0 for description, done in tasks: if not done: outstanding = outstanding + 1 print(f" {len(tasks)} tasks, {outstanding} still to do")
Step 2 — the safe index helper
def ask_index(prompt): """Ask for a task number, return its index, or -1 if unusable.""" if not tasks: print("Nothing in the list yet.") return -1 raw = input(prompt).strip() if not raw.isdigit(): print("Please type a number.") return -1 number = int(raw) if number < 1 or number > len(tasks): print(f"Choose between 1 and {len(tasks)}.") return -1 return number - 1
Step 3 — create, update, delete
def add_task(): """Create - append a new record.""" description = input("New task: ").strip() if description == "": print("A task needs a description.") return tasks.append([description, False]) print("Added:", description) def edit_task(): """Update - change one field of one record.""" i = ask_index("Edit which number? ") if i == -1: return print("Current:", tasks[i][0]) new_text = input("New wording (Enter to keep): ").strip() if new_text != "": tasks[i][0] = new_text tasks[i][1] = not tasks[i][1] print("Updated:", tasks[i][0], "- done:", tasks[i][1]) def delete_task(): """Delete - pop, so the record can be reported.""" i = ask_index("Delete which number? ") if i == -1: return removed = tasks.pop(i) print("Deleted:", removed[0])
Step 4 — the menu loop
while True: print() print("1 View 2 Add 3 Edit 4 Delete 5 Sort 6 Quit") choice = input("Choose: ").strip() if choice == "1": show_all() elif choice == "2": add_task() elif choice == "3": edit_task() elif choice == "4": delete_task() elif choice == "5": tasks.sort(key=lambda task: task[0].lower()) print("Sorted by description.") show_all() elif choice == "6": print("Goodbye!") break else: print("Please choose 1 to 6.")
A short session
1 View 2 Add 3 Edit 4 Delete 5 Sort 6 Quit Choose: 1 # Done Task 1 no Finish Sejarah homework 2 yes Practise piano 3 no Buy gift for Ella 3 tasks, 2 still to do Choose: 2 New task: Revise for Maths test Added: Revise for Maths test Choose: 3 Edit which number? 1 Current: Finish Sejarah homework New wording (Enter to keep): Updated: Finish Sejarah homework - done: True Choose: 4 Delete which number? 9 Choose between 1 and 4. Choose: 6 Goodbye!
The out-of-range delete is the important line: it refused politely and the loop carried on. Nothing was lost.
lambda is doing in the sortkey=lambda task: task[0].lower() is a one-line function with no name, sorting by the description in lower case. You could write a named def instead — Section 8 · Comprehensions & Idioms covers lambda properly.
Try It Yourself
13 minAdd a menu option that asks for a word and prints every task containing it, with its number. Say "no matches" when there are none.
Hint
def find_task(): wanted = input("Search for: ").strip().lower() matches = 0 for i in range(len(tasks)): if wanted in tasks[i][0].lower(): print(" ", i + 1, "-", tasks[i][0]) matches = matches + 1 if matches == 0: print("No matches.")
Give every task a third field: a priority from 1 to 3, asked for when it is added and validated like the menu choice.
- Show the priority in the table.
- Change the sort option to offer two orders: by description, or by priority with the most urgent first.
- Add a menu option that deletes every completed task at once — remembering the loop trap from LST-03.
Mini-Challenge 🔥 · Make It Yours
8 minTurn the manager into something you would actually open: a homework tracker, a Ramadan shopping list, a football squad, a game collection, a revision timetable.
It works if:
- All four CRUD operations are there and reachable from the menu.
- Each record has at least three fields.
- Nothing you can type at any prompt makes it crash — including a letter where a number belongs, and a number that is too big.
- Deleting the last record leaves an empty list that still views and still adds.
- At least one sort, and one summary line such as a count or a total.
If you finish early: add an undo that restores the last deleted record — pop() hands it back, so keep it in a second list.
Delete everything, then view. Type abc at every prompt. Choose record 0, then 99, then -1. A program that survives all of that is finished; one that has only been tried the sensible way is not.
Recap · The Whole Section
3 minSix lessons, and this project used all of them. Lists are numbered from 0 and sliced up to but not including the end; they are mutable, so they change in place and a copy needs [:]; append and pop grow and shrink them; in, index, count and the built-ins answer questions without loops; and sorted with key= orders them however you like. CRUD is those tools behind a menu.
Vocabulary Card
- CRUD
- Create, Read, Update, Delete — the four operations behind most apps.
- menu loop
- A
while Truethat shows options and only ends onbreak. - record
- One small list holding all the fields of a single thing.
- validation
- Checking typed input before using it — empty, not a number, out of range.
Where next. Section 3 · Dictionaries replaces task[0] and task[1] with task["description"], so records stop depending on remembering which position means what. Section 4 · File Handling makes the list survive closing the program.
Homework
4 minFinish your own manager and bring the file plus a screenshot of a session.
- Your own subject, with at least three fields per record.
- All four CRUD operations, plus a sort and a summary line.
- Every prompt validated — show in your screenshot that a bad input is refused politely.
- One extra feature of your choosing: search, undo, bulk delete, or a filter.
Sample · squad.py
# squad.py - a football squad manager with undo squad = [ ["Felix", "Striker", 9], ["Leo", "Midfield", 8], ["Theo", "Defender", 4], ] bin_list = [] def show_all(): print() if not squad: print(" (empty squad)") return print(f" {'#':<4}{'Name':<10}{'Position':<10}{'No':>3}") for i in range(len(squad)): name, position, number = squad[i] print(f" {i + 1:<4}{name:<10}{position:<10}{number:>3}") print(f" {len(squad)} players") def ask_index(prompt): if not squad: print("Squad is empty.") return -1 raw = input(prompt).strip() if not raw.isdigit(): print("Please type a number.") return -1 n = int(raw) if n < 1 or n > len(squad): print(f"Choose between 1 and {len(squad)}.") return -1 return n - 1 def add_player(): name = input("Name: ").strip().title() if name == "": print("A player needs a name.") return position = input("Position: ").strip().title() raw = input("Shirt number: ").strip() if not raw.isdigit(): print("Shirt number must be digits.") return squad.append([name, position, int(raw)]) print("Signed:", name) def edit_player(): i = ask_index("Edit which number? ") if i == -1: return print("Current position:", squad[i][1]) new_position = input("New position (Enter to keep): ").strip().title() if new_position != "": squad[i][1] = new_position print("Updated:", squad[i][0], "-", squad[i][1]) def delete_player(): i = ask_index("Release which number? ") if i == -1: return bin_list.append(squad.pop(i)) print("Released:", bin_list[-1][0]) def undo_delete(): if not bin_list: print("Nothing to undo.") return squad.append(bin_list.pop()) print("Back in the squad:", squad[-1][0]) while True: print() print("1 View 2 Sign 3 Edit 4 Release 5 Undo 6 Sort 7 Quit") choice = input("Choose: ").strip() if choice == "1": show_all() elif choice == "2": add_player() elif choice == "3": edit_player() elif choice == "4": delete_player() elif choice == "5": undo_delete() elif choice == "6": squad.sort(key=lambda player: player[2]) print("Sorted by shirt number.") show_all() elif choice == "7": print("Goodbye!") break else: print("Please choose 1 to 7.")
Output
1 View 2 Sign 3 Edit 4 Release 5 Undo 6 Sort 7 Quit Choose: 4 Release which number? 2 Released: Leo Choose: 5 Back in the squad: Leo Choose: 6 Sorted by shirt number. # Name Position No 1 Theo Defender 4 2 Leo Midfield 8 3 Felix Striker 9 3 players
Undo is just a second list: pop() hands the record back, append() parks it, and undo reverses both. Your subject will differ — what should match is that every prompt is guarded and an empty list never crashes anything.