← Home

100. Putting It All Together

Combine Python concepts to build a complete mini-application

100. Putting It All Together

It’s Saturday morning. Rohan opens his RAM manager. It works. But it’s a mess: 200-line main(), global variables everywhere, no way to test any piece in isolation.

He starts the refactor from scratch, applying everything:

class RAMManager:
    def __init__(self, threshold=90.0):
        if not isinstance(threshold, (int, float)) or not (0 < threshold <= 100):
            raise ValueError(f"threshold must be 0-100, got {threshold}")
        self.threshold = threshold
        self.snapshots = []

    def add_snapshot(self, percent: float, processes: list):
        self.snapshots.append({
            "percent": percent,
            "processes": processes,
            "is_critical": percent >= self.threshold,
        })

    def critical_count(self) -> int:
        return sum(1 for s in self.snapshots if s["is_critical"])

    def peak_usage(self) -> float:
        if not self.snapshots:
            return 0.0
        return max(s["percent"] for s in self.snapshots)

    def top_processes(self, n=5) -> list:
        latest = self.snapshots[-1]["processes"] if self.snapshots else []
        return sorted(latest, key=lambda p: p["rss_mb"], reverse=True)[:n]

Every method: one job. Inputs as parameters. No global state. Each can be tested with fake data.

This is the architecture that ships to production. The RAM manager is done.

100 exercises. You started with print("hello"). You just built a class that uses exception handling, isinstance, generator expressions, sorting, and list comprehensions — and you understand why each one is there.

💡 Fun fact: The principle “each method does one thing” is called the Single Responsibility Principle (SRP), coined by Robert C. Martin (“Uncle Bob”) in the early 2000s. It’s part of the SOLID principles that guide object-oriented design across virtually every programming language. A function that does one thing is also easier to name — if you struggle to name it, it probably does too much.

⚠️ Watch out: return False for invalid input is a silent failure. The caller gets False back and doesn’t know why the operation failed — they may silently continue with bad data. Always raise ValueError (or another appropriate exception) so the failure is impossible to ignore.

🤔 Think about it: The Gradebook.get_average() method calls self.grades[student] directly — what happens if the student doesn’t exist? Should get_average raise KeyError, return 0.0, or raise a custom StudentNotFoundError? What tradeoffs does each choice involve?

Learning objectives

  • Combine classes, exceptions, dicts, and list comprehensions in one program
  • Design a class with multiple cooperating methods
  • Apply sorting, filtering, and aggregation patterns from scratch
  • Understand the difference between returning False and raising ValueError

Key concepts

  • classes
  • dicts
  • list comprehensions
  • sorting
  • exceptions
  • aggregation

Try it

Concept detail

This exercise combines everything from chapters 1-5 into one class.

Classes bundle data (grades dict) with behavior (add_grade, get_average): self.grades = {“Alice”: {“Math”: 90, “English”: 80}, …}

Exceptions signal invalid input — never return False or None silently: raise ValueError(f“Score must be 0-100, got {score}“) Callers catch it or let it propagate. Either way, the failure is visible.

Dict operations: self.grades[student].values() — all scores for a student (dict_values object) list(…) — convert to list so sum/len work subject in self.grades[s] — safe check before accessing nested key

List comprehension with filter: [self.grades[s][subject] for s in self.grades if subject in self.grades[s]] — only includes students who have a grade for this subject

Sorting by a specific field, descending: avgs.sort(key=lambda x: x[1], reverse=True) x[1] is the average; sort descending so the highest average is first

Aggregation functions: min(scores), max(scores), sum(scores), len(scores) — no imports needed, these are built-in

Design principle: each method should do ONE thing. add_grade — validates and stores get_average — computes from stored data top_students — ranks by average subject_stats — aggregates across students

Solution

class Gradebook:
    def __init__(self):
        self.grades = {}

    def add_grade(self, student, subject, score):
        if score < 0 or score > 100:
            raise ValueError(f"Score must be 0-100, got {score}")
        if student not in self.grades:
            self.grades[student] = {}
        self.grades[student][subject] = score

    def get_average(self, student):
        scores = list(self.grades[student].values())
        return sum(scores) / len(scores)

    def top_students(self, n):
        avgs = [(student, self.get_average(student)) for student in self.grades]
        avgs.sort(key=lambda x: x[1], reverse=True)
        return avgs[:n]

    def subject_stats(self, subject):
        scores = [self.grades[s][subject] for s in self.grades if subject in self.grades[s]]
        return {
            "min": min(scores),
            "max": max(scores),
            "mean": sum(scores) / len(scores),
            "count": len(scores)
        }

Tests

def test_add_grade():
    gb = Gradebook()
    gb.add_grade("Alice", "Math", 90)
    assert gb.grades["Alice"]["Math"] == 90

def test_add_grade_invalid():
    gb = Gradebook()
    with pytest.raises(ValueError):
        gb.add_grade("Bob", "Math", 110)

def test_get_average():
    gb = Gradebook()
    gb.add_grade("Alice", "Math", 80)
    gb.add_grade("Alice", "English", 90)
    assert gb.get_average("Alice") == 85.0

def test_top_students():
    gb = Gradebook()
    gb.add_grade("Alice", "Math", 95)
    gb.add_grade("Bob", "Math", 75)
    gb.add_grade("Carol", "Math", 85)
    result = gb.top_students(2)
    assert result[0][0] == "Alice"
    assert result[1][0] == "Carol"

def test_subject_stats():
    gb = Gradebook()
    gb.add_grade("Alice", "Math", 80)
    gb.add_grade("Bob", "Math", 90)
    gb.add_grade("Carol", "Math", 70)
    stats = gb.subject_stats("Math")
    assert stats["min"] == 70
    assert stats["max"] == 90
    assert abs(stats["mean"] - 80.0) < 0.001
    assert stats["count"] == 3

Resources