Session 10 of the Python Fundamentals series, covered in depth. It designs programs out of small functions, covering local variables and scope, parameters as local names, reading outer variables, and keeping one job per function. It then covers composition, returning several values as a tuple and unpacking them, and builds a menu program in which each option is its own function. The traps are using a local variable outside its function, which raises a NameError, and expecting a reassigned parameter to change the caller's variable. Every snippet and error message was copied verbatim from CPython 3.12.
Subject: Python Fundamentals · 95 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Python Fundamentals - Session 10
One job per function - scope, composition, and clean design
Objectives
Functions take inputs and return outputs. Now we combine many small ones into a real program. By the end you can:
Warm-up
Discussion prompt
Before we open Session 10 - Functions, Deeper (Scope & Design): without looking back, what was the main idea of Session 9 - Functions, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
That session covers def, parameters, return against print, the None you get from a function with no return, default and keyword arguments, composition, and refactoring.
Section
Part 1
Concept
A variable created inside a function exists only inside that function. When the function ends, it is gone.
scope — Where a name is visible. A variable made inside a function has local scope - it cannot be seen from outside.
Counterexample
Discussion prompt
A variable created inside a function exists only inside that function. When the function ends, it is gone.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Fill the middle
Fill in the blanks
From A local variable at work — one line has had its right-hand side removed. Put it back.
def f():
msg = "inside"
print(msg)
f()
Why: msg is what everything below it consumes, so the wrong expression here fails later and somewhere else. It is created when f runs and used right there.
Worked example
def f():
msg = "inside"
print(msg)
f()msg lives inside f
Why: It is created when f runs and used right there.
Read the output
Why: Verified by execution: prints inside. msg does not exist anywhere else.
| name | where it exists |
|---|---|
| msg | inside f only |
Error analysis
Annotate
Walk the callouts on A local variable at work. Each one is a place this is easy to get subtly wrong.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Trying to read a function's local variable after the call.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It was local to f. Outside, the name is unknown, so Python raises a NameError.
return it so the outside can have it.
Why: It was local to f. Outside, the name is unknown, so Python raises a NameError.
Trap
Trying to read a function's local variable after the call.
def f():
secret = 1
f()
print(secret)secret is gone once f returns
Why: It was local to f. Outside, the name is unknown, so Python raises a NameError.
| you write | result |
|---|---|
| print(secret) | NameError: name 'secret' is not defined |
return it so the outside can have it.
def f():
secret = 1
return secret
value = f()
print(value)return carries the value out
Why: The caller stores it in value. Real output: 1. A return is the door a local value leaves through.
| you write | prints |
|---|---|
| print(value) | 1 |
Break the constraint
Discussion prompt
The rule this trap just fixed:
The caller stores it in value. Real output: 1. A return is the door a local value leaves through.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
It was local to f. Outside, the name is unknown, so Python raises a NameError.
Concept
The parameters in a def are local names, created fresh for each call and gone when it ends.
So def add(a, b) cannot leak a or b to the rest of the program.
Analogy
Discussion prompt
Explain Parameters are local too by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
The parameters in a def are local names, created fresh for each call and gone when it ends.
Intuition
Picture each function call setting up a private desk. Its local variables live on that desk and are cleared away when the call finishes.
This is a feature: two functions can both use a variable named i without interfering, because each has its own desk.
Explain it
Discussion prompt
Explain Each call gets its own workspace to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Picture each function call setting up a private desk. Its local variables live on that desk and are cleared away when the call finishes.
Concept
If a function assigns to a name that also exists outside, it makes a new local one. Changing it does not touch the outer variable.
Explain it to yourself
Discussion prompt
In Inner x, outer x this move is made:
The outer x is untouched
Why is that legal? Name the rule or definition it rests on before you read on.
Hint: If you can only say "because that is what you do", the rule is the thing to go and find.
Answer:
Verified by execution: inner: 99, then outer: 10. Two different x's.
Worked example
x = 10
def g():
x = 99
print("inner:", x)
g()
print("outer:", x)The x inside g is its own local
Why: Assigning x = 99 inside g creates a separate local x.
The outer x is untouched
Why: Verified by execution: inner: 99, then outer: 10. Two different x's.
| where | x |
|---|---|
| inside g | 99 |
| outside | 10 |
Comparison
Comparison matrix
From Inner x, outer x: refill the x column from what you know. The rest of the table is as it appeared.
| where | x |
|---|---|
| inside g | 99 |
| outside | 10 |
Concept
A function may read a variable from the surrounding program if it does not assign to it locally.
Still, passing values in as parameters is clearer than reaching out to outside variables - it makes the function self-contained.
Worked example
total = 5
def show_total():
print("reads outer:", total)
show_total()show_total reads total from outside
Why: It never assigns total, so Python looks outward and finds 5.
Read the output
Why: Verified by execution: reads outer: 5. Prefer a parameter for this - it is easier to test.
| total (outer) | prints |
|---|---|
| 5 | reads outer: 5 |
Blank canvas
Draw it
Draw what Reading an outer value just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Section
Part 2
Concept
A good function has a single, clear job. If you catch yourself joining two jobs with 'and', that is two functions.
Small functions are easier to name, test, and reuse.
Concept
ask_number, average, is_passing - each name tells you exactly what its function does and returns.
If a name needs 'and' (read_and_average_and_print), split it.
Faded example
Fill in the blanks
Split reading from computing, with the scaffolding fading: two lines are gone now — fill both.
def ask_number(prompt):
return int(input(prompt))
def average(a, b):
return (a + b) / 2
x = ask_number("First: ")
y = ask_number("Second: ")
print(average(x, y))
Why: Reproducing these unaided, rather than reading them, is what tells you the method has transferred. ask_number handles input; average handles the math.
Worked example
def ask_number(prompt):
return int(input(prompt))
def average(a, b):
return (a + b) / 2
x = ask_number("First: ")
y = ask_number("Second: ")
print(average(x, y))One function reads, one computes
Why: ask_number handles input; average handles the math. Each is simple.
The main code just wires them together
Why: Verified by execution: typing 8 and 4 prints 6.0. Small pieces, clear flow.
| function | its one job |
|---|---|
| ask_number | read a number |
| average | compute the mean |
Trade off
Comparison matrix
From Split reading from computing: every row here is a choice with a cost. Fill the its one job column, then say which row you would actually pick and what you give up for it.
| function | its one job |
|---|---|
| ask_number | read a number |
| average | compute the mean |
Intuition
Think of functions as building blocks. Each does one job well; you snap them together to build something bigger.
A big block that does everything is hard to reuse. Small blocks fit many projects.
Concept
A helper is a small function used by other functions. It hides a fiddly detail behind a clear name.
For example, a clean(text) helper that trims and lowercases input, used wherever you compare user text.
Section
Part 3
Concept
A function's body can call other functions. Complex behavior becomes a short list of well-named calls.
Worked example
def average(a, b):
return (a + b) / 2
def midpoint_label(a, b):
return "midpoint is " + str(average(a, b))
print(midpoint_label(8, 4))midpoint_label calls average
Why: It reuses average instead of re-deriving the math.
Read the output
Why: Verified by execution: midpoint is 6.0.
| call | uses | returns |
|---|---|---|
| midpoint_label(8, 4) | average(8, 4) = 6.0 | midpoint is 6.0 |
Worked example
def clean(s):
return s.strip().lower()
def is_yes(s):
return clean(s) == "yes"
print(is_yes(" YES "))is_yes leans on clean
Why: clean trims spaces and lowercases; is_yes just compares the cleaned text to "yes".
Read the output
Why: Verified by execution: ' YES ' cleans to 'yes', so is_yes is True.
| input | clean(input) | is_yes |
|---|---|---|
| YES | yes | True |
Sorting
Sort into buckets
These are the pieces of Session 10 - Functions, Deeper (Scope & Design), out of order. Put each one back under the part of the lesson it belongs to.
Concept
str(average(a, b)) runs the inner call first: compute the average, then convert it to text.
When calls nest, work from the innermost parentheses outward - just like arithmetic.
Section
Part 4
Concept
Separate values with commas in a return: return min(nums), max(nums). The function hands back both, as a pair.
Worked example
def min_max(nums):
return min(nums), max(nums)
print(min_max([3, 9, 1, 7]))Two values come back as a pair
Why: min is 1, max is 9, returned together.
Read the output
Why: Verified by execution: (1, 9) - a tuple holding both.
| call | returns |
|---|---|
| min_max([3, 9, 1, 7]) | (1, 9) |
Concept
Catch the two values in two variables: lo, hi = min_max(nums). Each name gets one of the returned values, in order.
Worked example
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 9, 1, 7])
print("low", lo, "high", hi)lo and hi split the returned pair
Why: lo gets the first (1), hi the second (9).
Read the output
Why: Verified by execution: low 1 high 9.
| name | gets |
|---|---|
| lo | 1 |
| hi | 9 |
Section
Part 5
Concept
In a menu program, give each option its own function. The main loop shows the menu and calls the right one.
This keeps each action small and the main loop short and readable.
Pattern
Predict first
The table runs: show_menu | print options · greet | say hello
In Menu handlers, given the rows so far: what is the next one — the row where function is add?
Correct: add | print 2 + 2
| function | job |
|---|---|
| show_menu | print options |
| greet | say hello |
| add | print 2 + 2 |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. show_menu prints choices; greet and add each do one thing.
Worked example
def show_menu():
print("1) Greet 2) Add 3) Quit")
def greet():
print("Hello!")
def add():
print(2 + 2)
show_menu()
greet()Each option is a named function
Why: show_menu prints choices; greet and add each do one thing.
Read the output
Why: Verified by execution: the menu line, then Hello!
| function | job |
|---|---|
| show_menu | print options |
| greet | say hello |
| add | print 2 + 2 |
Pattern
Step through it
Step through Menu handlers one row at a time. What is driving the change, and what would the row after the last one be?
Concept
A while loop shows the menu, reads the choice, and calls the matching function - then repeats until the user quits.
The loop stays tiny because all the real work is inside the handler functions.
Faded example
Fill in the blanks
The dispatch loop, with the scaffolding fading: two lines are gone now — fill both.
def greet():
print("Hello!")
running = True
while running:
choice = input("1) Greet 3) Quit: ")
if choice == "1":
greet()
elif choice == "3":
running = False
Why: Reproducing these unaided, rather than reading them, is what tells you the method has transferred. Choice 1 calls greet; choice 3 flips the flag to stop.
Worked example
The user types 1, then 3:
def greet():
print("Hello!")
running = True
while running:
choice = input("1) Greet 3) Quit: ")
if choice == "1":
greet()
elif choice == "3":
running = FalseThe loop reads a choice and calls a handler
Why: Choice 1 calls greet; choice 3 flips the flag to stop.
Trace the passes
Why: Verified by execution: prints Hello! then quits.
| choice | action |
|---|---|
| 1 | greet() -> Hello! |
| 3 | running = False -> stop |
Comparison
Comparison matrix
From The dispatch loop: refill the action column from what you know. The rest of the table is as it appeared.
| choice | action |
|---|---|
| 1 | greet() -> Hello! |
| 3 | running = False -> stop |
Section
Part 6
Concept
If a function reads input, does math, AND prints, split it into a reader, a calculator, and a display step.
Each piece is then testable on its own, and reusable elsewhere.
Fill the middle
Fill in the blanks
From Before: one function does it all — one line has had its right-hand side removed. Put it back.
def do_everything(a, b):
result = (a + b) / 2
print("The average is", result)
return result
do_everything(8, 4)
Why: result is what everything below it consumes, so the wrong expression here fails later and somewhere else. Three jobs tangled together - hard to reuse the math without also printing.
Worked example
def do_everything(a, b):
result = (a + b) / 2
print("The average is", result)
return result
do_everything(8, 4)It computes, prints, and returns
Why: Three jobs tangled together - hard to reuse the math without also printing.
Read the output
Why: Verified by execution: prints The average is 6.0. It works, but it is not flexible.
| jobs in one function |
|---|
| compute + print + return |
Worked example
def average(a, b):
return (a + b) / 2
result = average(8, 4)
print("The average is", result)average just computes; the caller decides to print
Why: Now average is reusable anywhere - in a comparison, a total, or silently.
Read the output
Why: Verified by execution: same result, but the pieces are separate and reusable.
| function | one job |
|---|---|
| average | compute the mean |
| (main code) | decide to print |
Section
Part 7
Anomaly
Predict first
A student writes this, and it looks reasonable:
Expecting a function to change the caller's variable by reassigning its parameter.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Reassigning n inside only changes the local n.
return the new value and assign it back.
Why: Reassigning n inside only changes the local n. The caller's x is untouched, so it stays 5.
Trap
Expecting a function to change the caller's variable by reassigning its parameter.
def add_one(n):
n = n + 1
x = 5
add_one(x)
print(x)n is a local copy of the value
Why: Reassigning n inside only changes the local n. The caller's x is untouched, so it stays 5.
| step | x |
|---|---|
| before | 5 |
| after add_one(x) | 5 (unchanged) |
return the new value and assign it back.
def add_one(n):
return n + 1
x = 5
x = add_one(x)
print(x)The caller stores the returned value
Why: x = add_one(x) rebinds x to 6. Real output: 6. To change a value, return it - do not rely on side effects.
| step | x |
|---|---|
| after x = add_one(x) | 6 |
Concept
A function that takes what it needs as parameters and hands back a result is easy to understand and test in isolation.
Reaching out to outside variables or relying on side effects makes functions harder to reuse and reason about.
Section
Part 8
Concept
Values that never change - a limit, a tax rate - are often set once near the top in ALL_CAPS, then read by functions below.
Reading a constant is a fine use of an outer variable, because it is a fixed setting, not shifting state.
Explain it to yourself
Discussion prompt
In A shared limit this move is made:
remaining reads the constant
Why is that legal? Name the rule or definition it rests on before you read on.
Hint: If you can only say "because that is what you do", the rule is the thing to go and find.
Answer:
MAX_TRIES is a fixed setting the function can rely on.
Worked example
MAX_TRIES = 3
def remaining(used):
return MAX_TRIES - used
print(remaining(1))remaining reads the constant
Why: MAX_TRIES is a fixed setting the function can rely on.
Read the output
Why: Verified by execution: 3 - 1 = 2.
| used | remaining |
|---|---|
| 1 | 2 |
Blank canvas
Draw it
Draw what A shared limit just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
Build a list locally with [] and append, then return it. The caller receives the finished list.
Pattern
Predict first
The table runs: 1 | [2] · 2 | [2, 4] · 3 | [2, 4, 6]
In Return a built list, given the rows so far: what is the next one — the row where i is 4?
Correct: 4 | [2, 4, 6, 8]
| i | out |
|---|---|
| 1 | [2] |
| 2 | [2, 4] |
| 3 | [2, 4, 6] |
| 4 | [2, 4, 6, 8] |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. The list is built inside, then handed back whole.
Worked example
def make_evens(n):
out = []
for i in range(1, n + 1):
out.append(i * 2)
return out
print(make_evens(4))out is local; return carries it out
Why: The list is built inside, then handed back whole.
Read the output
Why: Verified by execution: [2, 4, 6, 8].
| i | out |
|---|---|
| 1 | [2] |
| 2 | [2, 4] |
| 3 | [2, 4, 6] |
| 4 | [2, 4, 6, 8] |
Trade off
Comparison matrix
From Return a built list: every row here is a choice with a cost. Fill the out column, then say which row you would actually pick and what you give up for it.
| i | out |
|---|---|
| 1 | [2] |
| 2 | [2, 4] |
| 3 | [2, 4, 6] |
| 4 | [2, 4, 6, 8] |
Concept
A list passed to a function is shared, so a method like .append() on it does affect the caller's list.
This is different from a number: changing a list's contents in place is visible outside, because both names point at the same list.
Explain it
Discussion prompt
Explain Methods change a list in place to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
A list passed to a function is shared, so a method like .append() on it does affect the caller's list.
Fill the middle
Fill in the blanks
From append inside persists — one line has had its right-hand side removed. Put it back.
def add_item(lst, x):
lst.append(x)
items = [1, 2]
add_item(items, 3)
print(items)
Why: items is what everything below it consumes, so the wrong expression here fails later and somewhere else. append changes the shared list's contents in place.
Worked example
def add_item(lst, x):
lst.append(x)
items = [1, 2]
add_item(items, 3)
print(items)lst and items are the same list
Why: append changes the shared list's contents in place.
The change is visible outside
Why: Verified by execution: prints [1, 2, 3].
| step | items |
|---|---|
| before | [1, 2] |
| after add_item | [1, 2, 3] |
Comparison
Comparison matrix
From append inside persists: refill the items column from what you know. The rest of the table is as it appeared.
| step | items |
|---|---|
| before | [1, 2] |
| after add_item | [1, 2, 3] |
Anomaly
Predict first
A student writes this, and it looks reasonable:
Reassigning the parameter to a new list, expecting the caller to see it.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: It points the local lst at a brand-new list; the caller's r still points at the old one.
Change it in place, or return the new list.
Why: It points the local lst at a brand-new list; the caller's r still points at the old one. Same rule as with numbers.
Trap
Reassigning the parameter to a new list, expecting the caller to see it.
def replace(lst):
lst = [9, 9]
r = [1, 2]
replace(r)
print(r)lst = [9, 9] rebinds only the local name
Why: It points the local lst at a brand-new list; the caller's r still points at the old one. Same rule as with numbers.
| step | r |
|---|---|
| after replace | [1, 2] (unchanged) |
Change it in place, or return the new list.
def replace(lst):
return [9, 9]
r = [1, 2]
r = replace(r)
print(r)Return and reassign
Why: r = replace(r) rebinds r to the new list. Real output: [9, 9]. (Or clear-and-extend the same list in place.)
| step | r |
|---|---|
| after r = replace(r) | [9, 9] |
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
def are local names, created fresh for each call and gone when it ends.; Picture each function call setting up a private desk. Its local variables live on that desk and are cleared away when the call finishes.Intuition
Two names on the same list are two labels on one box. Editing the box's contents (append) is seen by both labels.
But lst = [9, 9] moves only that label to a different box - the other label stays on the original. Rebinding never reaches the caller.
Analogy
Discussion prompt
Explain Change-in-place vs rebind by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Two names on the same list are two labels on one box. Editing the box's contents (append) is seen by both labels.
Concept
A short comment above a function - or a line of text just under the def - explains its purpose to the next reader.
Say what it takes, what it returns, and any surprise. Future you will be grateful.
Counterexample
Discussion prompt
Say what it takes, what it returns, and any surprise. Future you will be grateful.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Worked example
def average(a, b):
# returns the mean of two numbers (a float)
return (a + b) / 2
print(average(8, 4))One comment states the job
Why: The reader knows what average does without tracing the math.
Read the output
Why: Verified by execution: 6.0. The comment is for humans; Python ignores it.
| function | documented job |
|---|---|
| average | mean of two numbers |
Error analysis
Annotate
Walk the callouts on A documented helper. Each one is a place this is easy to get subtly wrong.
Section
Part 9
Pattern
1. One job per function; name it after that job
Why: If the name needs 'and', split it into two.
2. Take inputs as parameters, hand results back with return
Why: Self-contained functions are easy to test and reuse.
3. Compose: let functions call smaller functions
Why: Build big behavior from small, tested pieces; read nested calls inside-out.
4. Return several values with commas, unpack with lo, hi = ...
Why: One call can hand back a pair (or more).
Explain it to yourself
Discussion prompt
In Scope rules of thumb this move is made:
Made inside a function -> local, invisible outside
Why is that legal? Name the rule or definition it rests on before you read on.
Hint: If you can only say "because that is what you do", the rule is the thing to go and find.
Answer:
Reading it outside is a NameError; return it to carry it out.
Pattern
Made inside a function -> local, invisible outside
Why: Reading it outside is a NameError; return it to carry it out.
Assigning inside makes a new local (shadowing)
Why: It does not change a same-named outer variable.
Reassigning a parameter does not change the caller
Why: Return the new value and let the caller reassign.
Real world
Discussion prompt
Outside this lesson: where does Session 10 - Functions, Deeper (Scope & Design) actually turn up? Name one concrete situation — a job, a piece of software someone ships, a decision somebody has to make — and say which part of Scope rules of thumb is doing the work in it.
Hint: Vague is the failure mode here. "Engineering" is not a situation; "deciding whether this build is fast enough to ship" is.
Answer:
Session 10 of the Python Fundamentals series, in depth. Designing programs from small functions: local variables and scope, parameters as local names, reading outer variables, one-job-per-function, composition (functions calling functions), returning several values as a tuple and unpacking them, and a menu program where each option is its own function.
Check
Is msg visible outside?
def f():
msg = "hi"
f()
print(msg)| msg exists | outside? |
|---|---|
| inside f | ? |
Check your understanding
What happens?
Answer: A
Why: msg is local to f and gone after the call, so print(msg) outside raises a NameError. Returning msg would carry it out. Verified by execution.
Check
Does the inner assignment change the outer x?
x = 1
def g():
x = 2
g()
print(x)| inner x | outer x |
|---|---|
| 2 | ? |
Check your understanding
What does this print?
Answer: A
Why: Assigning x = 2 inside g creates a separate local x. The outer x stays 1. Verified by execution.
Check
Trace x.
def bump(n):
n = n + 10
x = 5
bump(x)
print(x)| step | x |
|---|---|
| after bump | ? |
Check your understanding
What does this print?
Answer: A
Why: bump reassigns its local n, which does not affect the caller's x. Since bump does not return anything and x is never reassigned, x stays 5. Verified by execution.
Check
What do a and b get?
def two():
return 1, 2
a, b = two()
print(b)| a | b |
|---|---|
| 1 | ? |
Check your understanding
What does this print?
Answer: A
Why: two returns the pair 1, 2. Unpacking gives a = 1 and b = 2, so print(b) shows 2. Verified by execution.
Check
Work inside-out.
def dbl(n):
return n * 2
def inc(n):
return n + 1
print(inc(dbl(3)))| dbl(3) | inc(...) |
|---|---|
| ? | ? |
Check your understanding
What does this print?
Answer: A
Why: Inner call first: dbl(3) is 6. Then inc(6) is 7. Verified by execution.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Local Variables & Scope · One Job per Function · Functions Calling Functions · Returning More than One · A Menu Program · Refactor: One Job at a Time. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
Design programs from small, single-job functions. Variables inside are local; carry results out with return. Compose small functions into big behavior.
| Idea | In short |
|---|---|
| scope | made inside = local, invisible outside |
| shadowing | inner assignment makes a new local |
| composition | functions call smaller functions |
| multiple returns | return a, b then lo, hi = f() |
| change a value | return it; do not reassign a parameter |
One job per function, named for that job. Next session we sharpen a tool you have used all along: strings - cleaning and shaping text.
Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.