In this penultimate lesson we bring persistence to our To-Do App: saving tasks to a .txt file and loading them automatically when the program starts again.
In previous lessons we built a small To-Do App in Python with add, edit, and remove functionality. But every time we closed the program, all tasks were lost. In this step we solve that problem by adding data persistence with a plain text file.
Why Save to a File?
Without persistence, the app is only useful while it’s running. Users expect their tasks to be there when they come back. Common persistence options are:
- Plain text file (.txt) — simplest option (what we use here).
- Structured formats — JSON or CSV.
- Databases — SQLite, MySQL, PostgreSQL, etc.
This step uses a plain .txt file for clarity and simplicity.
1) Write Tasks to a .txt File
tasks = ["Buy milk", "Finish project", "Read a book"]
with open("tasks.txt", "w", encoding="utf-8") as f:
for task in tasks:
f.write(task + "\n")
print("Tasks saved!")
Notes: "w" overwrites the file each time; encoding="utf-8" avoids issues with special characters; and the with statement ensures the file closes properly.
2) Load Tasks at Startup
loaded = []
try:
with open("tasks.txt", "r", encoding="utf-8") as f:
loaded = [line.strip() for line in f]
print("Loaded:", loaded)
except FileNotFoundError:
print("No saved tasks yet.")
3) Full Example — To-Do App with Save/Load
Here is the complete CLI app with add / edit / remove plus automatic save/load. It saves on every change and once again when quitting.
from typing import List
FILENAME = "tasks.txt"
# ---------- Persistence Layer ----------
def load_tasks(filename: str = FILENAME) -> List[str]:
tasks: List[str] = []
try:
with open(filename, "r", encoding="utf-8") as f:
for line in f:
task = line.strip()
if task:
tasks.append(task)
except FileNotFoundError:
pass
return tasks
def save_tasks(tasks: List[str], filename: str = FILENAME) -> None:
with open(filename, "w", encoding="utf-8") as f:
for task in tasks:
f.write(task + "\n")
# ---------- Helpers ----------
def print_tasks(tasks: List[str]) -> None:
if not tasks:
print("\n[No tasks yet]\n")
return
print("\nCurrent tasks:")
for i, t in enumerate(tasks, start=1):
print(f" {i}. {t}")
print()
def add_task(tasks: List[str]) -> None:
new_task = input("Enter new task: ").strip()
if not new_task:
print("Empty task not added.")
return
tasks.append(new_task)
save_tasks(tasks)
print("Task added and saved.")
def remove_task(tasks: List[str]) -> None:
print_tasks(tasks)
if not tasks:
return
idx_str = input("Enter the task number to remove: ").strip()
if not idx_str.isdigit():
print("Please enter a valid number.")
return
idx = int(idx_str)
if 1 <= idx <= len(tasks):
removed = tasks.pop(idx - 1)
save_tasks(tasks)
print(f"Removed: '{removed}' (saved).")
else:
print("Invalid task number.")
def edit_task(tasks: List[str]) -> None:
print_tasks(tasks)
if not tasks:
return
idx_str = input("Enter the task number to edit: ").strip()
if not idx_str.isdigit():
print("Please enter a valid number.")
return
idx = int(idx_str)
if 1 <= idx <= len(tasks):
current = tasks[idx - 1]
print(f"Current: {current}")
new_text = input("Enter the new text: ").strip()
if not new_text:
print("Empty text — task unchanged.")
return
tasks[idx - 1] = new_text
save_tasks(tasks)
print("Task updated and saved.")
else:
print("Invalid task number.")
# ---------- CLI ----------
def main() -> None:
print("=== Step 10.6 — Save to File ===")
tasks = load_tasks()
print(f"Loaded {len(tasks)} task(s).")
while True:
print_tasks(tasks)
print("Choose an action:")
print("[a] Add [e] Edit [r] Remove [q] Quit")
choice = input("> ").strip().lower()
if choice == "a":
add_task(tasks)
elif choice == "e":
edit_task(tasks)
elif choice == "r":
remove_task(tasks)
elif choice == "q":
save_tasks(tasks)
print("Tasks saved. Goodbye!")
break
else:
print("Unknown option. Please choose a, e, r, or q.")
if __name__ == "__main__":
main()
Python vs PHP (Quick Compare)
- Python: very concise file I/O (
open,with,read/write), excellent for learning fundamentals. - PHP: similar primitives (
fopen,fwrite,fgets), but in web apps you usually move to databases or JSON for scalability.
What’s Next (Step 10.7)
In the final lesson we’ll improve persistence using JSON, which allows us to store richer data such as status, due date, or priority. You’ll see how simple it is to switch formats while keeping clean app logic.
Homework:
- Add a
[l]“List” command that prints tasks without other actions. - Show a clear message if the user tries to remove or edit an out-of-range index.
- Prevent duplicates: if the user adds a task that already exists, skip or ask confirmation.
- Also save the last modified date/time next to the task (still using
.txt).
Tags: #Python #BeginnerFriendly #FileHandling #ToDoApp #ProgrammingBasics #Persistence #LearnToCode
