Python Basics — Mini Projects
Mini Projects
The best way to learn programming is to build things. Here are 5 complete projects that use everything you’ve learned. Each project is broken into steps — try coding each step before looking at the solution.
Project 1: CLI Calculator
A command-line calculator that supports basic operations and keeps a history.
Skills used: functions, loops, error handling, dictionaries
Requirements
- Support +, -, *, / operations
- Keep a history of calculations
- Handle invalid input gracefully
- Let the user type “history” to see past calculations
- Type “quit” to exit
Show Solution
def calculator():
"""Interactive CLI calculator with history."""
history = []
operations = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else "Error: Division by zero",
}
print("=" * 40)
print(" Python Calculator")
print(" Type 'history' or 'quit'")
print("=" * 40)
while True:
user_input = input("\n> ").strip().lower()
if user_input == "quit":
print(f"\nGoodbye! ({len(history)} calculations performed)")
break
if user_input == "history":
if not history:
print("No calculations yet.")
else:
print("\n--- History ---")
for i, entry in enumerate(history, 1):
print(f" {i}. {entry}")
continue
try:
parts = user_input.split()
if len(parts) != 3:
print("Format: <number> <operator> <number>")
print("Example: 10 + 5")
continue
num1 = float(parts[0])
op = parts[1]
num2 = float(parts[2])
if op not in operations:
print(f"Unknown operator '{op}'. Use: +, -, *, /")
continue
result = operations[op](num1, num2)
if isinstance(result, str): # Error message
print(f" {result}")
else:
display = f"{num1} {op} {num2} = {result}"
print(f" {display}")
history.append(display)
except ValueError:
print("Invalid numbers. Example: 10 + 5")
# Run: calculator()
Project 2: To-Do List Manager
A file-based to-do list that persists between runs.
Skills used: file I/O, JSON, functions, loops, error handling
Requirements
- Add, complete, delete, and list tasks
- Tasks saved to a JSON file (persists between runs)
- Each task has: title, status (pending/done), creation date
- Display tasks with numbering
Show Solution
import json
import os
from datetime import datetime
TODO_FILE = "todos.json"
def load_todos():
"""Load todos from file."""
if os.path.exists(TODO_FILE):
with open(TODO_FILE, "r") as f:
return json.load(f)
return []
def save_todos(todos):
"""Save todos to file."""
with open(TODO_FILE, "w") as f:
json.dump(todos, f, indent=2)
def show_todos(todos):
"""Display all todos."""
if not todos:
print("\n 📋 No tasks yet. Add one with 'add'.")
return
print(f"\n 📋 To-Do List ({len(todos)} tasks)")
print(" " + "-" * 40)
for i, todo in enumerate(todos, 1):
status = "✅" if todo["done"] else "⬜"
print(f" {i}. {status} {todo['title']}")
print(f" Added: {todo['created']}")
def add_todo(todos):
"""Add a new task."""
title = input(" Task title: ").strip()
if not title:
print(" Title cannot be empty.")
return
todos.append({
"title": title,
"done": False,
"created": datetime.now().strftime("%Y-%m-%d %H:%M"),
})
save_todos(todos)
print(f" Added: '{title}'")
def complete_todo(todos):
"""Mark a task as complete."""
show_todos(todos)
if not todos:
return
try:
num = int(input(" Complete task #: "))
if 1 <= num <= len(todos):
todos[num - 1]["done"] = True
save_todos(todos)
print(f" Completed: '{todos[num - 1]['title']}'")
else:
print(f" Invalid number. Choose 1-{len(todos)}")
except ValueError:
print(" Please enter a number.")
def delete_todo(todos):
"""Delete a task."""
show_todos(todos)
if not todos:
return
try:
num = int(input(" Delete task #: "))
if 1 <= num <= len(todos):
removed = todos.pop(num - 1)
save_todos(todos)
print(f" Deleted: '{removed['title']}'")
else:
print(f" Invalid number. Choose 1-{len(todos)}")
except ValueError:
print(" Please enter a number.")
def main():
"""Main application loop."""
todos = load_todos()
commands = {
"list": lambda: show_todos(todos),
"add": lambda: add_todo(todos),
"done": lambda: complete_todo(todos),
"delete": lambda: delete_todo(todos),
}
print("\n 📝 To-Do Manager")
print(" Commands: list, add, done, delete, quit")
while True:
cmd = input("\n> ").strip().lower()
if cmd == "quit":
print(" Goodbye!")
break
elif cmd in commands:
commands[cmd]()
else:
print(" Unknown command. Use: list, add, done, delete, quit")
# Run: main()
Project 3: Number Guessing Game
A game where the computer picks a random number and the player tries to guess it.
Skills used: random module, loops, conditionals, input validation
Requirements
- Random number between 1 and 100
- Give “higher/lower” hints after each guess
- Track number of attempts
- Show high score (fewest guesses)
- Option to play again
Show Solution
import random
def play_round():
"""Play one round of the guessing game."""
secret = random.randint(1, 100)
attempts = 0
print("\n 🎯 I'm thinking of a number between 1 and 100...")
while True:
try:
guess = int(input(" Your guess: "))
except ValueError:
print(" Please enter a valid number.")
continue
attempts += 1
if guess < secret:
print(" 📈 Higher!")
elif guess > secret:
print(" 📉 Lower!")
else:
print(f" 🎉 Correct! You got it in {attempts} attempts!")
return attempts
def guessing_game():
"""Main game loop with high score tracking."""
high_score = None
games_played = 0
print("=" * 40)
print(" Number Guessing Game")
print("=" * 40)
while True:
attempts = play_round()
games_played += 1
if high_score is None or attempts < high_score:
high_score = attempts
print(f" 🏆 New high score: {high_score} attempts!")
else:
print(f" High score: {high_score} attempts")
print(f" Games played: {games_played}")
again = input("\n Play again? (y/n): ").strip().lower()
if again != "y":
print(f"\n Thanks for playing! Best: {high_score} attempts in {games_played} games.")
break
# Run: guessing_game()
Project 4: Password Generator
Generate secure, customizable passwords.
Skills used: random, string module, functions, command-line arguments
Requirements
- Configurable length (default 16)
- Toggle: uppercase, lowercase, digits, special characters
- Option to exclude ambiguous characters (0/O, 1/l/I)
- Generate multiple passwords at once
- Check password strength
Show Solution
import random
import string
def generate_password(length=16, uppercase=True, lowercase=True,
digits=True, special=True, exclude_ambiguous=False):
"""Generate a secure random password."""
chars = ""
required = [] # Ensure at least one of each selected type
if lowercase:
lc = string.ascii_lowercase
if exclude_ambiguous:
lc = lc.replace("l", "")
chars += lc
required.append(random.choice(lc))
if uppercase:
uc = string.ascii_uppercase
if exclude_ambiguous:
uc = uc.replace("O", "").replace("I", "")
chars += uc
required.append(random.choice(uc))
if digits:
dg = string.digits
if exclude_ambiguous:
dg = dg.replace("0", "").replace("1", "")
chars += dg
required.append(random.choice(dg))
if special:
sp = "!@#$%^&*()-_=+[]{}|;:,.<>?"
chars += sp
required.append(random.choice(sp))
if not chars:
raise ValueError("At least one character type must be selected")
if length < len(required):
raise ValueError(f"Length must be at least {len(required)}")
# Fill remaining length with random chars
remaining = length - len(required)
password_chars = required + [random.choice(chars) for _ in range(remaining)]
# Shuffle so required chars aren't always at the start
random.shuffle(password_chars)
return "".join(password_chars)
def check_strength(password):
"""Rate password strength."""
score = 0
checks = {
"Length >= 8": len(password) >= 8,
"Length >= 12": len(password) >= 12,
"Length >= 16": len(password) >= 16,
"Has lowercase": any(c.islower() for c in password),
"Has uppercase": any(c.isupper() for c in password),
"Has digits": any(c.isdigit() for c in password),
"Has special": any(c in string.punctuation for c in password),
}
for check, passed in checks.items():
if passed:
score += 1
if score <= 3:
strength = "Weak"
elif score <= 5:
strength = "Medium"
else:
strength = "Strong"
return strength, score, checks
def password_generator():
"""Interactive password generator."""
print("=" * 40)
print(" 🔐 Password Generator")
print("=" * 40)
try:
count = int(input(" How many passwords? [3]: ").strip() or "3")
length = int(input(" Password length? [16]: ").strip() or "16")
except ValueError:
count, length = 3, 16
print(f"\n Generated {count} passwords (length={length}):\n")
for i in range(count):
pw = generate_password(length=length)
strength, score, _ = check_strength(pw)
print(f" {i+1}. {pw} [{strength} {score}/7]")
# Run: password_generator()
Project 5: Contact Book (File-Based)
A contact management system that stores contacts in a CSV file.
Skills used: CSV, file I/O, functions, error handling, data structures
Requirements
- Add, search, edit, delete contacts
- Fields: name, phone, email
- Store in CSV file
- Case-insensitive search by name
- Display contacts in a formatted table
Show Solution
import csv
import os
CONTACTS_FILE = "contacts.csv"
FIELDNAMES = ["name", "phone", "email"]
def load_contacts():
"""Load contacts from CSV file."""
contacts = []
if os.path.exists(CONTACTS_FILE):
with open(CONTACTS_FILE, "r", newline="") as f:
reader = csv.DictReader(f)
contacts = list(reader)
return contacts
def save_contacts(contacts):
"""Save contacts to CSV file."""
with open(CONTACTS_FILE, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
writer.writeheader()
writer.writerows(contacts)
def show_contacts(contacts):
"""Display all contacts."""
if not contacts:
print("\n 📒 No contacts yet.")
return
print(f"\n 📒 Contacts ({len(contacts)})")
print(f" {'#':<4} {'Name':<20} {'Phone':<15} {'Email':<25}")
print(" " + "-" * 64)
for i, c in enumerate(contacts, 1):
print(f" {i:<4} {c['name']:<20} {c['phone']:<15} {c['email']:<25}")
def add_contact(contacts):
"""Add a new contact."""
name = input(" Name: ").strip()
phone = input(" Phone: ").strip()
email = input(" Email: ").strip()
if not name:
print(" Name is required.")
return
contacts.append({"name": name, "phone": phone, "email": email})
save_contacts(contacts)
print(f" Added: {name}")
def search_contacts(contacts):
"""Search contacts by name (case-insensitive)."""
query = input(" Search name: ").strip().lower()
if not query:
return
results = [c for c in contacts if query in c["name"].lower()]
if results:
print(f"\n Found {len(results)} match(es):")
for c in results:
print(f" {c['name']} | {c['phone']} | {c['email']}")
else:
print(f" No contacts matching '{query}'")
def edit_contact(contacts):
"""Edit an existing contact."""
show_contacts(contacts)
if not contacts:
return
try:
num = int(input(" Edit contact #: ")) - 1
if not 0 <= num < len(contacts):
print(" Invalid number.")
return
except ValueError:
print(" Please enter a number.")
return
contact = contacts[num]
print(f" Editing: {contact['name']} (press Enter to keep current value)")
name = input(f" Name [{contact['name']}]: ").strip() or contact["name"]
phone = input(f" Phone [{contact['phone']}]: ").strip() or contact["phone"]
email = input(f" Email [{contact['email']}]: ").strip() or contact["email"]
contacts[num] = {"name": name, "phone": phone, "email": email}
save_contacts(contacts)
print(f" Updated: {name}")
def delete_contact(contacts):
"""Delete a contact."""
show_contacts(contacts)
if not contacts:
return
try:
num = int(input(" Delete contact #: ")) - 1
if 0 <= num < len(contacts):
removed = contacts.pop(num)
save_contacts(contacts)
print(f" Deleted: {removed['name']}")
else:
print(" Invalid number.")
except ValueError:
print(" Please enter a number.")
def main():
"""Main application loop."""
contacts = load_contacts()
commands = {
"list": lambda: show_contacts(contacts),
"add": lambda: add_contact(contacts),
"search": lambda: search_contacts(contacts),
"edit": lambda: edit_contact(contacts),
"delete": lambda: delete_contact(contacts),
}
print("\n 📒 Contact Book")
print(" Commands: list, add, search, edit, delete, quit")
while True:
cmd = input("\n> ").strip().lower()
if cmd == "quit":
print(f" Goodbye! ({len(contacts)} contacts saved)")
break
elif cmd in commands:
commands[cmd]()
else:
print(" Unknown command. Use: list, add, search, edit, delete, quit")
# Run: main()
Building Tips
- Start small — get the basic version working first
- Test often — run your code after every few lines
- Add features incrementally — don’t try to build everything at once
- Handle errors — think about what could go wrong
- Read your own code — if it’s confusing, simplify it
Next up: Coding Challenges →