Python Projects (Beginner)
Course 4 · Chapter 8 · Capstone: Expense Tracker
💰 Capstone: Expense Tracker
The final project — and the final chapter of the entire Python track. An expense tracker ties together everything this course has built: the load/save JSON pattern from Chapters 4-6, dict-based records, dict-based summarization (Course 1, Chapter 7's word-frequency idea, here counting money instead of words), and — for the first time in this course — try/except built directly into the app itself, rather than left as an extension.
🎮 What We're Building
An expense tracker: log expenses with an amount, category, and description; list them all; see totals by category and overall; delete a mistaken entry — all persisted to JSON, and all resilient to bad input.
Step 1: Representing an Expense
expense = {"amount": 12.50, "category": "Food", "description": "Lunch"}
Step 2: Load & Save
import json
from pathlib import Path
FILE = Path("expenses.json")
def load_expenses():
if not FILE.exists():
return []
return json.loads(FILE.read_text())
def save_expenses(expenses):
FILE.write_text(json.dumps(expenses, indent=2))
By this point in the course, this pattern should feel completely familiar — the fourth time this exact load_/save_ shape has appeared, after the to-do app, contact book, and scraper.
Step 3: Adding an Expense — With Real Error Handling
def add_expense(expenses):
try:
amount = float(input("Amount: "))
except ValueError:
print("That's not a valid number — expense not added.")
return
if amount <= 0:
print("Amount must be positive — expense not added.")
return
category = input("Category: ")
description = input("Description: ")
expenses.append({"amount": amount, "category": category, "description": description})
print("Expense added.")
Chapters 1-7 flagged crash-prone float(input(...)) calls in warn-boxes and left the fix as an extension. Here, it's built in directly: try/except ValueError (Course 2, Chapter 3) catches non-numeric input cleanly, and a guard clause (Chapter 2's own technique) rejects a zero or negative amount as a separate, sensible business rule — two different kinds of "bad input," handled with the two different tools each one actually calls for.
Step 4: Viewing and Summarizing
def list_expenses(expenses):
if not expenses:
print("No expenses recorded yet.")
return
for i, e in enumerate(expenses, start=1):
print(f"{i}. ${e['amount']:.2f} — {e['category']} — {e['description']}")
def total_by_category(expenses):
totals = {}
for e in expenses:
totals[e["category"]] = totals.get(e["category"], 0) + e["amount"]
return totals
def total_spending(expenses):
return sum(e["amount"] for e in expenses)
total_by_category reuses Course 1, Chapter 7's word-frequency-counter pattern exactly — totals.get(category, 0) + amount in place of counts.get(word, 0) + 1, summing dollar amounts per category instead of counting word occurrences. total_spending reuses the generator-expression-plus-sum() pattern from Course 2, Chapter 5 and Course 3's own performance chapter.
Step 5: Deleting an Expense
def delete_expense(expenses, index):
if 0 <= index < len(expenses):
del expenses[index]
print("Deleted.")
else:
print("Invalid expense number.")
Same bounds-checked del as Chapter 6's contact book — and same rule for the menu loop below: always list_expenses() immediately before asking for a number to delete, exactly the fix Chapter 6's warn-box called for.
🏁 The Complete Expense Tracker
import json
from pathlib import Path
FILE = Path("expenses.json")
def load_expenses():
if not FILE.exists():
return []
return json.loads(FILE.read_text())
def save_expenses(expenses):
FILE.write_text(json.dumps(expenses, indent=2))
def add_expense(expenses):
try:
amount = float(input("Amount: "))
except ValueError:
print("That's not a valid number — expense not added.")
return
if amount <= 0:
print("Amount must be positive — expense not added.")
return
category = input("Category: ")
description = input("Description: ")
expenses.append({"amount": amount, "category": category, "description": description})
print("Expense added.")
def list_expenses(expenses):
if not expenses:
print("No expenses recorded yet.")
return
for i, e in enumerate(expenses, start=1):
print(f"{i}. ${e['amount']:.2f} — {e['category']} — {e['description']}")
def total_by_category(expenses):
totals = {}
for e in expenses:
totals[e["category"]] = totals.get(e["category"], 0) + e["amount"]
return totals
def total_spending(expenses):
return sum(e["amount"] for e in expenses)
def delete_expense(expenses, index):
if 0 <= index < len(expenses):
del expenses[index]
print("Deleted.")
else:
print("Invalid expense number.")
expenses = load_expenses()
while True:
print("\n1. Add 2. List 3. Totals by Category 4. Total Spending 5. Delete 6. Quit")
choice = input("Choose an option: ")
if choice == "1":
add_expense(expenses)
save_expenses(expenses)
elif choice == "2":
list_expenses(expenses)
elif choice == "3":
for category, total in total_by_category(expenses).items():
print(f"{category}: ${total:.2f}")
elif choice == "4":
print(f"Total spending: ${total_spending(expenses):.2f}")
elif choice == "5":
list_expenses(expenses)
try:
num = int(input("Expense number to delete: "))
except ValueError:
print("That's not a valid number.")
continue
delete_expense(expenses, num - 1)
save_expenses(expenses)
elif choice == "6":
print("Goodbye!")
break
else:
print("Not a valid option, try again.")
Option 5's own try/except ValueError around int(input(...)) reuses continue from Course 1, Chapter 4 — skipping straight back to the top of the menu loop rather than letting a bad number crash the whole program, the same protective instinct add_expense applied to the amount field.
File I/O
load_expenses/save_expenses — the same JSON pattern from every chapter since 4.
Functions
Each responsibility — add, list, total, delete — is its own small function.
Dict-based summarization
total_by_category reuses Course 1's word-frequency-counter pattern.
Error handling, built in
try/except around every risky input — not left as an exercise this time.
🎓 Course Complete!
That's all 8 projects of Python Projects — a guessing game, calculator, password generator, to-do app, quiz, contact book, web scraper, and this expense tracker capstone — and with it, the complete Python track: Fundamentals → Intermediate → Advanced → Projects, 33 chapters across 4 courses. Every tool from every course ended up here, put to real, practical use.