Scope: variables live where they are made
In a program with dozens of functions, names would collide constantly if every variable were visible everywhere: two functions both using total would silently corrupt each other. Scope is the isolation rule that prevents that, and it is what makes a function readable on its own, without studying the rest of the file.
A variable created inside a function, including its parameters, is local: it exists only while that call runs, then vanishes. The world outside never sees it.
n = 10 def show(n): n = n + 1 print("inside:", n) show(100) print("outside:", n) # still 10
The n inside is a different variable that merely shares the name. This isolation is a feature: a function cannot accidentally trample your other variables, and you can read it without knowing the rest of the program.
Rule of thumb for now: functions take what they need as parameters and hand results back with return. Reaching out to touch outside variables makes code hard to trace.
Local and outer names that merely look alike
The function receives 100 and modifies its own copy. The outer n is never touched.
n = 10 def show(n): n = n + 1 print("inside:", n) show(100) print("outside:", n)
Output
inside: 101 outside: 10
Two different variables share the name here. The parameter n was created fresh when the call began, given the value 100, incremented to 101, and discarded when the call ended. The n at module level still holds 10 afterwards, because nothing inside the function could reach it.
That isolation is worth relying on deliberately. It means a function can use short, obvious names like n or total without any risk of colliding with variables elsewhere, and it means reading the function requires no knowledge of the surrounding file.
Decomposition: small functions, composed
Big problems fall apart into function-sized pieces. Grading a student is really two steps you already know: the average from lesson 6-3 and the letter chain from lesson 4-1. Give each a name:
def average(nums): return sum(nums) / len(nums) def letter(avg): if avg >= 90: return "A" ...
Now the top level reads like the plan: letter(average(scores)). Each piece can be tested alone, reused, and fixed without touching the others. This is the single most important habit the mini-projects in unit 10 will demand.
Composing average and letter
Two small functions, each recognizable from an earlier lesson, combine into a grading step that reads like its own description.
def average(nums): return sum(nums) / len(nums) def letter(avg): if avg >= 90: return "A" if avg >= 80: return "B" if avg >= 70: return "C" return "F" scores = [85, 92, 78] print(f"{average(scores):.1f}") print(letter(average(scores)))
Output
85.0
Baverage is the built-in pair from lesson 6-3 wrapped in a name, and letter is the threshold chain from lesson 4-1 written in the early-return style. The three scores sum to 255, so the mean is exactly 85.0, which clears the 80 threshold and falls short of 90, giving B.
The composition letter(average(scores)) is the payoff. The inner call runs first and its result becomes the argument to the outer one, so the line reads as a plan rather than a calculation. Each function can also be tested on its own, which is much harder when the same logic is inlined into one long block.
This snippet prints 5.
def mystery(a, b): if a > b: return a - b return b - a print(mystery(3, 8))
With a at 3 and b at 8, the condition 3 > 8 is False, so the first return is skipped and execution falls through to the second, which computes 8 - 3.
What the function actually does is return the positive difference between two numbers, subtracting in whichever order avoids a negative result. It is the early-return style from lesson 8-2 applied to a two-case problem, and a good name would say so, since mystery tells a reader nothing while distance or abs_difference would.