Tasks in Memory
Before a to-do app can save anything or take commands, it needs one thing: a way to hold a task in the program's memory. Get the shape of the data right and everything after this gets shorter. So that's where we start - no files, no commands yet, only tasks living in a list while the program runs.
What is a task, really?
A task isn't one value. It has a few parts: the text of the thing to do, whether it's finished, and an id so we can point at it later ("mark task 2 done"). When you have a bundle of named values like that, a Python dictionary is the natural fit.
Here's one task as a dict:
=
Run that. You get the whole dict, then two values pulled out by name. The keys - id, text, done - are how we reach inside. That's the entire data model for one task. No class, no library. A dict is plenty.
Many tasks: a list of dicts
One task is a dict. A to-do list is, fittingly, a Python list of those dicts:
Before you run this one, guess how many lines it prints. Then check.
=
A list of dictionaries is one of the most common shapes in all of Python. Rows from a database, items in a shopping cart, results from an API - they almost always arrive looking like this. Learn it here and you'll recognize it everywhere.
Adding a task
Right now we typed the tasks by hand. The app needs to add them on demand. We'll write a function that takes the current list and the new text, and appends a fresh dict to the end.
The one wrinkle is the id. Each task needs a number nobody else has, and the first task added to an empty list should get id 1.
Your turn. This function is the point of the phase, so have a go before you read on. Fill it in and hit Run: the checks underneath tell you whether it works. My version is in the next block whenever you want it.
# Append one new task dict to `tasks`. It needs three keys:
# "id" - a number no other task in the list has
# "text" - the text passed in
# "done" - False, because a new task isn't finished
# Adding to an empty list should produce id 1.
pass
# --- checks: fix your function until this prints "All good." ---
=
assert == , f
assert == 2, f
=
assert == , f
Stuck on the id? Ask what the ids already in the list can tell you.
One way to write it
= + 1
= 1
=
Walk through it. We start with an empty list. The first add_task sees no tasks, so new_id is 1. The next sees a max id of 1, so it picks 2. Then 3. The list grew from nothing to three tasks, each with its own id, and we never had to track a counter ourselves.
If you reached for len(tasks) + 1, that is a reasonable first instinct and it passes every check above. It breaks in Phase 3, when we add deleting. Delete task 2 from a list of three and len is 2, so len + 1 hands the next task an id of 3 - which already exists. Reading the actual max id keeps every id unique no matter what you've removed. It's a small choice now that saves a real bug later.
Listing what you have
Adding is half of it. The other half is showing the list back in a way a human wants to read. Let's make a list_tasks function that prints each task with its id and a marker for whether it's done.
Two tasks go in and one gets marked done. Before you run it: which line comes back with the x?
= + 1
return
=
=
= True # pretend we finished the first one
A couple of things to notice. We tightened add_task using max(..., default=0) - the default kicks in when the list is empty, so the if/else disappears and the function is one line of logic. Same behavior, less code.
In list_tasks, the mark line is a small conditional: "x" if the task is done, a space if not. The f"..." string drops the values straight into the text. The result reads like an actual checklist:
[x] 1. buy milk
[ ] 2. call the bank
That bracket-and-number format is something you can scan in a real terminal. We set tasks[0]["done"] = True by hand here only to show the marker working - in Phase 3 we'll write a proper function for it.
Where we are
You now have the spine of the app: a task is a dict, the list is a list of dicts, add_task grows it, and list_tasks shows it. Everything from here builds on this shape.
The catch - and you may have felt it - is that the moment the program ends, the list vanishes. Run it again and you're back to empty. That's the next problem to solve: making your tasks stick around. On to saving them to a file.