Day 3 Β· Recipes and kitchens

Functions, Scope & Modules

You will be able to
  • Define functions with parameters, defaults, *args and **kwargs, and call them correctly
  • Explain the difference between returning a value and printing it, and choose the right one
  • Predict which variable a name refers to using the LEGB scope rule
  • Import and use standard-library modules (math, random, datetime)
  • Write a module with a docstring and an if __name__ == "__main__" block
Today's ~120 minutes
Spaced-rep warm-up: Day 1–2 flashcards10 min
Concept study: ELI5 + tech (return vs print, LEGB, modules)20 min
Guided: recipe cards, scope experiments, stdlib tour45 min
Practice: refactor the path quiz into functions20 min
Project: the toolbox module15 min
Quiz + flashcards10 min

Builds on: Day 1 β€” Running scripts and reading errors Β· Day 2 β€” Variables, types, and control flow

The analogy

A recipe is instructions written once and cooked many times. You don't rewrite "how to make stock" inside every soup recipe β€” you write it on its own card, give it a name, and say "make stock" whenever you need it. A function is that card: def make_stock(bones, hours): names the recipe and lists its ingredient slots (parameters). Calling make_stock(chicken, 3) fills the slots and runs the card. Crucially, a good recipe *hands you the dish* at the end β€” that is return. Printing is different: it's the cook shouting "the stock is done!" from the kitchen. Nice to hear, but you can't pour a shout into tomorrow's soup. Return hands the value back so other code can use it.

Each time a recipe runs, it gets a clean kitchen counter: the bowls and spoons it uses (local variables) exist only during that cooking session and are cleared afterward. That's scope β€” names inside a function don't leak out and don't collide with yours. And a module is a whole recipe *book*: someone else's tested collection (import math) or your own file of recipes that any script can open.

Why this matters on the job

Functions are the unit of everything you will ship. Every API endpoint (Day 41), every tool you hand an LLM agent (Day 111), every test you write (Day 18) is "a function with a clear input and a returned output." The return-vs-print distinction is the first hard line between toy scripts and real software: code that prints can only be watched, code that returns can be tested, composed, and called by other systems. And FDEs live in other people's modules β€” on-site you'll read an unfamiliar codebase and your first question is always "what does this function take and what does it return?"

Guided practice

guided 1

Recipe cards β€” define, call, return

15 min
  1. Create week-01/recipes.py and type the starter code. Run it, then read the output against the code β€” match each printed line to the call that produced it.
  2. The shout_area vs area pair is today's core lesson. Add the line x = shout_area(3, 4) then print(x) β€” it prints None, because shouting isn't returning. Write one comment explaining why.
  3. Call greet three ways: greet("Ada"), greet("Ada", "?"), greet(punct="...", name="Ada"). Predict each output first.
  4. Add a new function total_minutes(days, hours_per_day=2) that RETURNS days * hours_per_day * 60. Use it to print the minutes in this 180-day program without typing the answer yourself.
  5. Delete the return line from area, rerun, and watch the arithmetic downstream break with a TypeError about NoneType β€” meet this error today so you recognize it forever.
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)
guided 2

Whose kitchen? β€” scope experiments

15 min
  1. Create scope_lab.py with the starter code, but DO NOT run it yet. Write your prediction for each numbered print as a comment.
  2. Run it. Section 1 shows locals dying with the call β€” the NameError at the end is correct behavior, not a bug. Comment out that line after reading the error.
  3. Section 2 is the shadowing trap: assigning score = ... inside the function created a brand-new local; the global never changed. Confirm by reading the two printed values.
  4. Section 3: fix add_bonus the professional way β€” take score as a parameter and return the new value, then rebind at the call site (score = add_bonus(score)). No global keyword needed, today or (almost) ever.
  5. In one sentence at the bottom: what does LEGB stand for, and which wins?
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)
guided 3

The recipe books β€” stdlib tour and the main guard

15 min
  1. Create toolbox.py with the starter code and run it directly: python toolbox.py. The demo lines under the guard print.
  2. Now create a second file use_toolbox.py containing just: import toolbox and print(toolbox.roll(20)). Run it β€” notice the demo lines did NOT print. Explain in a comment why the guard made that happen.
  3. In the REPL, take a 5-minute stdlib field trip: import math then try math.pi, math.sqrt(2), math.ceil(3.2). Then import random and run random.choice(["tea", "coffee"]) three times.
  4. Add a function days_until_done(current_day) to toolbox.py that returns 180 minus current_day, with a one-line docstring. Add a demo call for it under the guard.
  5. Run python -m toolbox from the same folder β€” the -m flag runs a module by name instead of by filename; it behaves the same here and matters more once packages arrive on Day 17.
🐍 python β€” editable, runs in your browser
Ctrl/⌘+Enter runs · Tab indents · numpy/pandas/sklearn auto-load on import (torch and network calls need a local run)

On your own

Refactor the path quiz

20 min

Open yesterday's pathquiz.py. It works, but it's one long scroll of repeated code β€” exactly what functions exist to fix.

Goal: refactor it so the quiz logic lives in functions: ask_question(prompt, answer) returns True/False for one question (case-insensitive comparison inside), verdict(score, total) returns the tier string, and a main() function runs the whole quiz. The bottom of the file should be nothing but the main guard calling main(). Behavior must be identical to yesterday's version.

Constraints: no global anywhere; the score is a local in main() updated from ask_question's return value; every function has a one-line docstring.

Hints (only if stuck): a function returning a bool can be used directly β€” if ask_question(...): score = score + 1. Test verdict() alone in the REPL by importing it: that's the payoff of the guard.

Ship before you stop

Your toolbox module

Grow toolbox.py into a real personal module you will import for weeks. It must contain at least five functions, each with a docstring and a return value (no print-only functions): roll(sides=6), today_stamp(), days_until_done(current_day), clamp(value, low, high) (returns value pinned into the range), and one function of your own invention. Add a demo block under if __name__ == "__main__": that exercises all five. Prove it works both ways: run it directly, then write use_toolbox.py that imports it and uses two functions without triggering the demo. Log in journal.md which function was hardest and why.

Rubric β€” check what you completed (0/5)

Common mistakes & misconceptions

  • Confusing printing with returning. print() shows a value to a human; return hands it to the calling code. A function that only prints cannot be tested, composed, or reused β€” and a forgotten return means the caller silently receives None.
  • Assigning to a global inside a function and expecting it to change. Assignment creates a local; the global is shadowed, not updated. Pass values in as parameters and return the result instead.
  • Using a mutable default like def add(item, bucket=[]). Defaults are evaluated ONCE at definition time, so every call shares the same list. Use bucket=None and create the list inside. (This bites harder after Day 4 β€” remember it.)
  • Putting demo/test code at module top level with no main guard, so it runs on every import. Guard it with if __name__ == "__main__".
  • Writing one 80-line function instead of several small ones. If you can't name what a function does in one short sentence, it's doing too much β€” split it.
  • Naming a script after a stdlib module (random.py, math.py). Your file shadows the real module and imports break with confusing errors. Check names before saving.
Knowledge check

Q1. def double(x): print(x * 2) β€” then y = double(4). What is y?

Q2. score = 10 at module level. Inside a function you write score = score + 1 and get UnboundLocalError. Why?

Q3. What does if __name__ == "__main__": accomplish?

Go deeper β€” curated resources

docsPython Tutorial β€” Defining Functions (4.7–4.9) β†—25 mindocsPython Tutorial β€” Modules and the import system β†—20 minbookAutomate the Boring Stuff β€” Ch. 3: Functions β†—30 min
If you have a third hour
  • Keyword-only and positional-only parameters β€” The / and * markers in signatures restrict how arguments may be passed β€” you will see them all over stdlib docs. Skim the "Special parameters" section of the functions tutorial once.
Done means
  • All three guided scripts run; the None-from-print experiment explained in a comment
  • pathquiz.py refactored: main() + guard, no globals, identical behavior
  • toolbox.py has 5 documented returning functions and imports cleanly from use_toolbox.py
  • Quiz β‰₯ 2/3 (reread the scope section and retake if lower)
How this connects

← Back: Day 2's quiz project becomes today's refactoring material β€” the same behavior, restructured into named, reusable pieces. The TypeError-with-NoneType you triggered traces straight back to Day 1's error-reading ritual.

Forward β†’: toolbox.py grows for weeks and gets packaged properly on Day 17. Functions with clear inputs and returns are exactly what Day 18 tests, what Day 41 turns into API endpoints, and what Day 111 hands to an LLM as tools. Day 12 revisits functions as VALUES you can wrap and pass around.

Unlocks: D4 Collections Β· D7 Week 1 Checkpoint: CLI Task Tracker Β· D9 Object-Oriented Python I Β· D12 Closures, Decorators & Functional Style