|
| 1 | +# Solution: Level 0 / Project 01 - Terminal Hello Lab |
| 2 | + |
| 3 | +> **STOP** — Have you attempted this project yourself first? |
| 4 | +> |
| 5 | +> Learning happens in the struggle, not in reading answers. |
| 6 | +> Spend at least 20 minutes trying before reading this solution. |
| 7 | +> If you are stuck, try the [Walkthrough](./WALKTHROUGH.md) first — it guides |
| 8 | +> your thinking without giving away the answer. |
| 9 | +
|
| 10 | +--- |
| 11 | + |
| 12 | + |
| 13 | +## Complete solution |
| 14 | + |
| 15 | +```python |
| 16 | +"""Level 0 project: Terminal Hello Lab. |
| 17 | +
|
| 18 | +Practice printing to the terminal, using variables, and |
| 19 | +understanding how Python sends text to your screen. |
| 20 | +
|
| 21 | +Concepts: print(), variables, string concatenation, f-strings, escape characters. |
| 22 | +""" |
| 23 | + |
| 24 | + |
| 25 | +# WHY greet is a function: Wrapping the greeting in a function makes it |
| 26 | +# reusable and testable. Tests can call greet("Ada") directly without |
| 27 | +# running the whole script or simulating user input. |
| 28 | +def greet(name: str) -> str: |
| 29 | + """Build a personalised greeting string.""" |
| 30 | + # WHY f-string: f"..." lets us embed variables directly inside the string. |
| 31 | + # It is cleaner than "Hello, " + name + "! Welcome to Python." |
| 32 | + return f"Hello, {name}! Welcome to Python." |
| 33 | + |
| 34 | + |
| 35 | +# WHY width defaults to 40: Default arguments let callers skip the parameter |
| 36 | +# when the common case is fine. A 40-character banner fits comfortably in |
| 37 | +# most terminals. |
| 38 | +def build_banner(title: str, width: int = 40) -> str: |
| 39 | + """Create a decorative banner around a title.""" |
| 40 | + # WHY "*" * width: String repetition creates a horizontal rule. |
| 41 | + # The * character repeated `width` times makes one border line. |
| 42 | + border = "*" * width |
| 43 | + |
| 44 | + # WHY .center(): It pads the title with spaces so it sits in the middle. |
| 45 | + # This keeps the output visually balanced regardless of the title length. |
| 46 | + centered_title = title.center(width) |
| 47 | + |
| 48 | + # WHY join with \n: We build all three lines and combine them with |
| 49 | + # newline characters so the function returns one complete string. |
| 50 | + return f"{border}\n{centered_title}\n{border}" |
| 51 | + |
| 52 | + |
| 53 | +# WHY build_info_card returns a dict: Dictionaries label each piece of data |
| 54 | +# with a key, making the output self-documenting. Code that receives the |
| 55 | +# dict can access card["name"] instead of guessing what index 0 means. |
| 56 | +def build_info_card(name: str, language: str, day: int) -> dict: |
| 57 | + """Collect key facts into a dictionary.""" |
| 58 | + return { |
| 59 | + "name": name, |
| 60 | + "language": language, |
| 61 | + "learning_day": day, |
| 62 | + # WHY call greet() here: Reusing the greet function avoids duplicating |
| 63 | + # the greeting logic. If the format changes, we only fix it once. |
| 64 | + "greeting": greet(name), |
| 65 | + } |
| 66 | + |
| 67 | + |
| 68 | +# WHY run_hello_lab exists: It groups all the "do stuff" steps into one |
| 69 | +# callable unit. The script's __main__ block stays tiny, and tests could |
| 70 | +# call this function to verify the full workflow. |
| 71 | +def run_hello_lab(name: str, day: int) -> dict: |
| 72 | + """Execute the full hello-lab workflow and return results.""" |
| 73 | + # --- Terminal output (side effects) --- |
| 74 | + banner = build_banner("TERMINAL HELLO LAB") |
| 75 | + print(banner) |
| 76 | + print() # WHY blank line: Visual breathing room between sections. |
| 77 | + |
| 78 | + greeting = greet(name) |
| 79 | + print(greeting) |
| 80 | + |
| 81 | + # WHY \t: The tab character indents the text, showing how escape |
| 82 | + # characters control formatting inside strings. |
| 83 | + print(f"\tDay {day} of your Python journey.") |
| 84 | + print() |
| 85 | + |
| 86 | + # WHY \n inside the string: Demonstrates that escape characters work |
| 87 | + # inside f-strings too — this prints on two lines from one print() call. |
| 88 | + print("Fun fact: Python is named after Monty Python,\nnot the snake!") |
| 89 | + |
| 90 | + # --- Build summary --- |
| 91 | + summary = build_info_card(name, "Python", day) |
| 92 | + return summary |
| 93 | + |
| 94 | + |
| 95 | +# WHY __name__ == "__main__": This guard means the code below only runs |
| 96 | +# when you execute the file directly (python project.py), NOT when |
| 97 | +# another file imports it. Tests import greet() and build_banner() |
| 98 | +# without triggering the interactive input prompts. |
| 99 | +if __name__ == "__main__": |
| 100 | + name = input("What is your name? ") |
| 101 | + day_text = input("What day of your Python journey is it? ") |
| 102 | + |
| 103 | + # WHY int(): input() always returns a string. We need an integer |
| 104 | + # for the day number so we can do math with it later. |
| 105 | + day = int(day_text) |
| 106 | + |
| 107 | + summary = run_hello_lab(name, day) |
| 108 | + |
| 109 | + print("\n--- Your Info Card ---") |
| 110 | + # WHY .items(): Iterating over key-value pairs lets us print |
| 111 | + # every field without knowing the exact keys in advance. |
| 112 | + for key, value in summary.items(): |
| 113 | + print(f" {key}: {value}") |
| 114 | +``` |
| 115 | + |
| 116 | +## Design decisions |
| 117 | + |
| 118 | +| Decision | Why | Alternative considered | |
| 119 | +|----------|-----|----------------------| |
| 120 | +| `greet()` as a standalone function | Makes the greeting testable in isolation — tests call `greet("Ada")` without running the whole script | Inline the greeting with `print(f"Hello, {name}!")` directly — simpler but untestable | |
| 121 | +| `build_banner()` uses a `width` default parameter | Callers get a sensible 40-char banner without passing extra arguments, but can customise when needed | Hard-code the width to 40 — less flexible if the title is very long | |
| 122 | +| `build_info_card()` returns a `dict` | Keys like `"name"` and `"language"` make data self-documenting; any code can access fields by name | Return a tuple `(name, language, day)` — shorter but relies on positional order, which is fragile | |
| 123 | +| `run_hello_lab()` both prints and returns | Lets the interactive script show output AND lets tests inspect the returned dict | Print-only with no return — tests would have to capture stdout, which is harder for beginners | |
| 124 | + |
| 125 | +## Alternative approaches |
| 126 | + |
| 127 | +### Approach B: String concatenation instead of f-strings |
| 128 | + |
| 129 | +```python |
| 130 | +def greet(name: str) -> str: |
| 131 | + # Using + to join strings instead of f-strings. |
| 132 | + return "Hello, " + name + "! Welcome to Python." |
| 133 | + |
| 134 | +def build_banner(title: str, width: int = 40) -> str: |
| 135 | + border = "*" * width |
| 136 | + # Using .format() instead of f-strings. |
| 137 | + centered_title = "{:^{}}".format(title, width) |
| 138 | + return border + "\n" + centered_title + "\n" + border |
| 139 | +``` |
| 140 | + |
| 141 | +**Trade-off:** String concatenation with `+` is the most basic approach and works in all Python versions. However, f-strings (available since Python 3.6) are easier to read when mixing text and variables. You can see at a glance what the output looks like. The `.format()` method is a middle ground — more powerful than `+` but less readable than f-strings. For beginners, f-strings are the recommended default. |
| 142 | + |
| 143 | +## What could go wrong |
| 144 | + |
| 145 | +| Scenario | What happens | Prevention | |
| 146 | +|----------|-------------|------------| |
| 147 | +| User presses Enter without typing a name (empty string) | `greet("")` returns `"Hello, ! Welcome to Python."` — an awkward blank space | Add a guard: `if not name.strip(): name = "friend"` before calling `greet()` | |
| 148 | +| User types letters for the day number (e.g. "seven") | `int("seven")` raises `ValueError` and the program crashes | Wrap `int(day_text)` in a `try/except ValueError` and ask again | |
| 149 | +| User types a negative day number (e.g. "-3") | `int("-3")` succeeds, and the program says "Day -3" — technically wrong | Check `if day < 1:` and ask again or default to 1 | |
| 150 | +| User types only spaces as their name | `greet(" ")` returns `"Hello, ! Welcome to Python."` — looks messy | Use `.strip()` on the name and check if it is empty after stripping | |
| 151 | + |
| 152 | +## Key takeaways |
| 153 | + |
| 154 | +1. **Functions make code testable.** By wrapping logic in `greet()` and `build_banner()`, tests can verify each piece independently without simulating user input. This is why every project from here on uses functions. |
| 155 | +2. **f-strings are your go-to for mixing text and variables.** The syntax `f"Hello, {name}!"` is clearer than concatenation and will be used in nearly every Python project you encounter. |
| 156 | +3. **The `if __name__ == "__main__"` guard separates "library code" from "script code."** This pattern appears in every project going forward and becomes essential when you start importing modules in Level 1+. |
| 157 | +4. **Dictionaries bundle related data with meaningful labels.** The `build_info_card()` function previews a pattern you will use constantly — returning structured data from functions instead of just printing it. |
0 commit comments