These questions ask what happens when a function is defined, called and completed. Keep those stages separate, then trace argument evaluation, parameter binding and the returned result in that order.
Follow the function lifecycle
At definition time, Python executes def, creates a function object and binds its name; the body does not run yet. In def twice(n): return n * 2, twice becomes a reference to that function. Functions are ordinary objects, so another name can store one, a call can receive one, and a function can return one. A lambda also creates a function, but its body is one expression whose value is returned implicitly, as in lambda n: n + 1.
At call time, evaluate every argument expression from left to right before entering the body. Then bind each result by position or by an allowed parameter name. A parameter cannot receive two values in one call. Binding shares the argument object rather than copying it, so def add(items): items.append(9) can mutate a list supplied by its caller. An executed return ends only the current call; if control reaches the end without one, the result is None.
| Parameter form | Binding method |
|---|---|
| An ordinary parameter | Fill it once by position or by its permitted name. |
| A parameter after a bare star or a starred positional collector | Supply it by name; additional positional values cannot fill it. |
| A starred positional collector | Gather unmatched positional values into a tuple, as def pack(*items): return items does. |
| A double-starred named collector | Gather unmatched named values into a dictionary, as a function whose parameter is written with a doubled star, collecting the extras into a dictionary does. |
Defaults belong to definition time too: the expression is evaluated once when def executes, and later omitted arguments reuse that resulting object. Inside a function, assigning to a name normally makes that name local unless a global or nonlocal declaration changes the rule. Decide scope from the function body, not from which branch happens to run.
Reject these tempting routes
| Tempting route | How to reject it |
|---|---|
| Run the body when def is encountered, or postpone creation until the first call | Definition creates and names the function; invocation is the separate event that enters its body. |
| Assume calls deep-copy mutable arguments | Track object identity through binding. Mutation through the parameter is visible through every reference to that object. |
| Infer a result from the final local name when return is absent | Only an executed return supplies an explicit result. Reaching the end produces None. |
| Recompute a default expression on every omitted call | Attach the resulting default object to the definition event and reuse it when the argument is absent. |
| Collect starred positional values in a list or named values in a tuple | Match the marker to its container: one star gathers a tuple; two stars gather a dictionary. |
| Let a marker affect earlier parameters, or forbid defaults on later named parameters | The marker changes following parameters only, and those parameters may still declare defaults. |
Try it
Assume total is a defined ordinary function that accepts these arguments, and count and value are defined and both return normally. For total(count(), text=value()), which evaluation statements are true? Select all that apply.
- The expressions wait and run lazily inside total.
- Both argument expressions finish before total starts.
- The keyword expression must run before every positional expression.
- count() is evaluated before value().
Given def total(count, text): pass, what happens when total(1, count=2, text="x") is called?
- count becomes 2 because the keyword overrides 1.
- It raises TypeError because count receives two values.
- It succeeds only because text is supplied last.
- It raises ValueError because keyword order is wrong.
Inside a function body, count is assigned without any global or nonlocal declaration. How does Python normally classify count in that function?
- It automatically updates a same-named global.
- It is local to the function by default.
- Its scope is chosen after the function returns.
- It is local only when its value is mutable.