Capstone: Expense Tracker

Python Projects — Capstone: Expense Tracker
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.