Variable Scope and Lifetime
The type of a function object is function. Inspecting the function name reveals that Python created a function value, just as inspecting another variable reveals the kind of value it holds.
Try it: Scope: Local, Enclosing, Global, Built-in
How Python looks up a name Local → Enclosing → Global → Built-in, why assigning to a name inside a function makes it local (and raises UnboundLocalError if it is read first), and what global, nonlocal and closures change.
How it works
- Before a function runs, Python marks every name assigned inside it as local to that function, unless it is declared global or nonlocal.
- Reading a name searches the local namespace, then enclosing functions, then the module's globals, then the built-ins; the first hit wins.
- A local name read before it has a value raises UnboundLocalError (Python does not fall back to the global).
- global x makes assignments go to the module namespace; nonlocal x makes them go to the nearest enclosing function.
- A function's local frame disappears when it returns, unless an inner function (a closure) still uses its variables.
Default run (10 steps): Ready: Local shadows global. Every name is looked up Local, then Enclosing, then Global, then Built-in. … Finished. Printed: inside: 99 / outside: 10.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Built-ins shows only the names the script used.
Loading the simulation…
A Name Can Hold Instructions
A function definition does more than make a reusable piece of code available. It creates a variable, and that variable holds a function object. This means a function name is a name referring to a value, much as x can hold 5 or name can hold the string Alice.
What Definition Creates
When Python defines print_lyrics, the name print_lyrics becomes a variable that holds a function object. The object contains the instructions written inside the function definition. The name is therefore not merely a label for invisible code: it refers to a value that Python can inspect, print, and pass to another function.
The type of a function object is function. Inspecting the function name reveals that Python created a function value, just as inspecting another variable reveals the kind of value it holds.
What do you think happens?
After defining print_lyrics, what kind of value does the name print_lyrics hold?
Reveal answer
A function object
A function definition creates a variable holding a function object, and that object's type is function.
Calling Changes the Execution Path
Defining a function creates the function object, but it does not mean that Python immediately runs every instruction in its body. Execution enters the body when the function is called. A custom function uses the same call syntax as a built-in function: the function name followed by parentheses.
The execution sequence is: Python reaches the call, pauses at that line, jumps into the function body, runs the statements in order, and then returns control to the line immediately after the call. The function call is therefore a temporary change in the execution path, not a replacement for the caller's remaining instructions.
Nested Calls and Returning Control
Tracing repeat_lyrics
Trace the control flow when repeat_lyrics calls print_lyrics twice.
- 1Start with the outer call
Execution reaches
repeat_lyrics(), so control moves from the original caller into the body ofrepeat_lyrics. - 2Enter the inner call
The first call to
print_lyricsmoves control into theprint_lyricsfunction body. Its statements run in order. - 3Return to the outer function
When the first
print_lyricscall finishes, control returns torepeat_lyricsat the point after that inner call. - 4Make the second inner call
The second call to
print_lyricsagain enters its body and runs the same instructions. - 5Return to the original caller
After
repeat_lyricsfinishes, control returns to the line immediately after the originalrepeat_lyricscall.
The flow is nested: original caller to repeat_lyrics, repeat_lyrics to print_lyrics, back to repeat_lyrics, into print_lyrics again, and finally back to the original caller.
This pattern is useful because a larger behavior can be assembled from smaller functions. repeat_lyrics does not need to duplicate the instructions inside print_lyrics; it reuses them by calling the function twice.
Scope and Lifetime Boundaries
For this topic, the important boundary is the distinction between the function name and a particular execution of the function body. Defining print_lyrics creates the variable holding the function object. Calling print_lyrics() transfers control into that object's instructions, and finishing the call transfers control back to the caller. The source establishes these creation and control-flow facts; it does not specify additional rules about when a function object is removed or about more advanced local and global name-scope rules.
Common Tracing Mistakes
Common mistake Treating a function name as only a label for hidden code.
What it looks likeThinking of print_lyrics as separate from the value it refers to.
Why it failsThe function name is a variable holding a function object.
Do this insteadThink of print_lyrics as a name referring to a value whose type is function.
Common mistake Assuming that defining a function immediately runs its body.
What it looks likeExpecting the statements inside print_lyrics to execute merely because the function was defined.
Why it failsThe body runs when the function is called.
Do this insteadSeparate the definition step from the call step.
Common mistake Forgetting the parentheses in a function call.
What it looks likeUsing the function name when the goal is to execute the function.
Why it failsThe call syntax is the function name followed by parentheses.
Do this insteadUse print_lyrics() or the equivalent name-and-parentheses form when calling a function.
Common mistake Tracing only the inner function in a nested call.
What it looks likeStopping the trace after print_lyrics finishes inside repeat_lyrics.
Why it failsControl first returns to
repeat_lyricsand later returns to the original caller.Do this insteadRecord every transfer: caller to outer function, outer function to inner function, back to outer function, then back to caller.
Execution Trace Practice
Using the source example with repeat_lyrics and print_lyrics, write the order in which control visits these places: the original caller, repeat_lyrics, the first print_lyrics call, repeat_lyrics again, the second print_lyrics call, and the line after repeat_lyrics.
Hints
- A function call moves control into the called function.
- After the called function finishes, control returns to the point after that call.
- The two calls to
print_lyricsoccur from insiderepeat_lyrics.
A complete trace should show both levels of nesting: the original caller resumes only after repeat_lyrics has completed, and repeat_lyrics resumes between its two calls to print_lyrics.
Key Takeaways
- 1Defining a function creates a variable that holds a function object.
- 2The function object's type is function, and the object can be inspected like other values.
- 3A custom function is called with its name followed by parentheses, just like a built-in function.
- 4A call moves control into the function body and returns control to the line after the call when the body finishes.
- 5Functions can call other functions, creating nested execution that returns outward one level at a time.
At a glance
- A function definition creates a variable holding a function object.
- A function object has type function and can be inspected as a value.
- Calling uses the
function_name()form and begins execution of the function body. - Control returns to the caller after the function finishes.
- Nested calls return control outward through each function that was waiting.