Functions: arguments, return values and scope
A function is a named block with its own local scope; arguments go in, one value comes back, and locals vanish when it ends.
A function packages a piece of work behind a name. You give it inputs, it returns a result, and the names it uses internally are invisible to the rest of the program. That last property — local scope — is what lets you write and test a function without knowing anything about its caller's variables.
A function is a boundary
Arguments cross into the function and a return value crosses back out. Everything else — the locals, the loop counters, the temporary lists — stays inside. Draw that boundary clearly and you can reason about each function on its own.
The stepper runs square(add(p, q)) and shows the call stack at every moment.
Each call pushes a frame with its own bindings; returning pops it.
Step through square(add(p, q)) and watch the call stack grow and shrink
source
def add(a, b):
return a + b
def square(n):
return n * n
result = square(add(2, 3))call stack (top = innermost)
<module>()
Start at the module body.
A local name lives only in the frame that bound it: after add returns, its a and b no longer exist anywhere. The module frame never sees them — it only receives the value that came back, which is why functions can be written and tested without knowing the caller's variables.
Parameters and arguments
The names in the def line are parameters; the values supplied at the call
are arguments. Python matches them in several ways:
- Positional:
add(2, 3)bindsa = 2,b = 3. - Keyword:
add(a=2, b=3)matches by name and can be reordered. - Default:
def greet(name, greeting="Hi")makes the second parameter optional when omitted. - Variadic:
def f(*args, **kwargs)collects extra positional arguments into a tuple and extra keyword arguments into a dict.
def add(a, b, c=0):
return a + b + c
add(1, 2) # 3
add(1, 2, c=10) # 13
add(a=1, b=2, c=3) # 6Never use a mutable default argument
def f(x, acc=[]) creates the list once, when the function is defined,
and every call that omits acc shares it — so appends leak between calls.
Use acc=None and create the list inside the body instead. This is one of
the classic Python bugs.
Return values
return ends the function immediately and hands one value back to the caller.
A function that reaches its end without return returns None. To hand back
several values, return a tuple and unpack it at the call site:
def min_max(xs):
return min(xs), max(xs)
low, high = min_max([3, 1, 4])Passing a mutable object as an argument does not copy it. A function that
calls xs.append(...) changes the caller's list; a function that computes a
new list and returns it does not. Prefer the returning style: it makes the data
flow explicit.
Scope and the lifetime of a frame
Each call creates a fresh local namespace. A free variable — one assigned inside the function — lives there and disappears when the function returns. The rules, in order of lookup:
- Local names, assigned inside the function.
- Enclosing names, from an outer function (read-only unless declared
nonlocal). - Global names, at module level (read-only inside a function unless
declared
global). - Built-in names such as
lenandprint.
This is the LEGB rule. It explains why assigning to a name inside a function
that you expected to modify a global silently creates a local instead — you must
write global counter or nonlocal counter to say otherwise.
Illustrative vs real
The stepper uses two tiny functions so the whole stack fits on screen. Real programs nest far deeper, but the rule is identical: a frame exists only for the duration of a call, and recursion simply stacks frames until the base case returns. The panel is the recursion you will meet again in trees and sorting.
Check yourself
Eduspheria wiki · Programming & Data Structures, Python foundations
0 / 5 answered
From the mid-term paper
Modeled on NITJ AI-503, Mid-Term October 2024
0 / 5 answered
Where next: classes — bundling data and the functions that operate on it into a single object.