Wiki
Core12 min read

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>()

no local bindings

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) binds a = 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) # 6

Never 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:

  1. Local names, assigned inside the function.
  2. Enclosing names, from an outer function (read-only unless declared nonlocal).
  3. Global names, at module level (read-only inside a function unless declared global).
  4. Built-in names such as len and print.

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

  1. 1What does a function return if it reaches its end without a return statement?
    Multiple choice
  2. 2The default value `acc=[]` is created fresh on every call.
    True / false
  3. 3Which keyword must a nested function use to rebind a variable of its enclosing function?
    Short answer
  4. 4In LEGB lookup, which scope is consulted first?
    Multiple choice
  5. 5How many values does the function call `min_max([3, 1, 4])` return to the caller?
    Numeric answer

From the mid-term paper

Modeled on NITJ AI-503, Mid-Term October 2024

0 / 5 answered

  1. 1A function returns the smallest positive integer divisible by every number from 1 to n. For n = 4, what is it?
    Numeric answer
  2. 2The list [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] is reversed. What is the sum of the first three elements of the reversed list?
    Numeric answer
  3. 3A function returns the sum of a list when its even and odd counts are equal, triples odd values when evens outnumber odds, and squares even values when odds outnumber evens. For the list [1, 2, 3, 4], what does it return?
    Numeric answer
  4. 4For the same function, applied to the list [2, 4, 6, 7], what is the sum of the list it returns?
    Numeric answer
  5. 5Which of these Python data types is immutable?
    Multiple choice

Where next: classes — bundling data and the functions that operate on it into a single object.