A function has a default argument of an empty list and appends to it, and a loop builds a list of lambdas that each read the loop variable. Both are reported as bugs. Explain what each one actually does, when the deciding evaluation happens, and give the standard fix for each.

A function has a default argument of an empty list and appends to it, and a loop builds a list of lambdas that each read the loop variable. Both are reported as bugs. Explain what each one actually does, when the deciding evaluation happens, and give the standard fix for each.

Approach: Ask when the default expression is evaluated relative to the calls, and when a closure reads the variable it captured relative to the loop finishing.

The default list is created once at function definition time and shared by every call that omits the argument, so the appends accumulate across calls, and each lambda reads the enclosing variable when it is called rather than when it was created, so all of them see the final value; the fixes are a default of None with the list built inside the body, and an extra parameter defaulting to the loop variable so the value is bound per iteration. Default arguments are evaluated once and stored on the function object, which is why any mutable default becomes shared state. The closure behaviour is late binding: the lambda holds a cell variable referring to the name, so the value is fetched at call time and a finished loop leaves every cell pointing at the last item. The default argument trick works because that default is itself evaluated on each iteration, and functools.partial binds the value the same way.

Follow-up: When is a mutable default the right tool, and how would you make the shared state explicit and safe to use?

Key concepts: mutable default argument, late binding, function definition time, cell variable.