Variable Scope and Lifetime
Defining a function creates a variable that holds a function object with type 'function'.
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
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.
Start with the outer call: Execution reaches repeat_lyrics(), so control moves from the original caller into the body of repeat_lyrics.
Enter the inner call: The first call to print_lyrics moves control into the print_lyrics function body. Its statements run in order.
Return to the outer function: When the first print_lyrics call finishes, control returns to repeat_lyrics at the point after that inner call.
Make the second inner call: The second call to print_lyrics again enters its body and runs the same instructions.
Return to the original caller: After repeat_lyrics finishes, control returns to the line immediately after the original repeat_lyrics call.
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
Treating a function name as only a label for hidden code.
The function name is a variable holding a function object.
Fix:
Think of print_lyrics as a name referring to a value whose type is function.Assuming that defining a function immediately runs its body.
The body runs when the function is called.
Fix:
Separate the definition step from the call step.Forgetting the parentheses in a function call.
The call syntax is the function name followed by parentheses.
Fix:
Use print_lyrics() or the equivalent name-and-parentheses form when calling a function.Tracing only the inner function in a nested call.
Control first returns to repeat_lyrics and later returns to the original caller.
Fix:
Record 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_lyrics occur from inside repeat_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
- Defining a function creates a variable that holds a function object.
- The function object's type is function, and the object can be inspected like other values.
- A custom function is called with its name followed by parentheses, just like a built-in function.
- A call moves control into the function body and returns control to the line after the call when the body finishes.
- Functions can call other functions, creating nested execution that returns outward one level at a time.
Key Takeaways
- 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.