In Python, a function is more than a list of commands, as we learnt in Koan 5. When a function is defined inside another, it becomes a closure, a small piece of code that remembers its parent's environment. This memory is powerful, but it's not a picture of the past. It's more like a window. It shows you the world as it is right now, not as it was when the window was first opened. This phenomenon is known as late binding.
Let's begin by observing the simplest expression of a closure.
Here, execute_commission
is the closure. It remembers the item
variable from its parent function, make_commission
. When we call draw_sun()
, it returns "sun" as expected. The closure has a clear, singular memory of its purpose.
Part 2: The Master's Three Commands
Now, let's observe the lazy calligrapher's technique, as described in the koan. He waits until all instructions have been given before beginning his work.
One might expect the output to be "sun," "moon," and then "cloud." However, the code will print "cloud" three times.
This is the very essence of late binding. The execute_commission
function doesn't capture the value of instruction
at the time it's created. Instead, it holds a reference to the variable instruction
itself. By the time we finally get around to executing the first function in the list, the for
loop has already completed, and the variable instruction
has a final value: "cloud."
All three closures refer to the same single instruction
variable in the outer scope, which has finished its journey.
Part 3: The Diligent Calligrapher
To correct this behavior, we need a way for each closure to capture its own unique copy of the instruction at the moment it's created. We must teach the calligrapher to be diligent and remember each command individually.
One method to achieve this is by using a default argument. As we learnt in Koan 3, default arguments are evaluated once, at the time the function is defined. We can modify our make_commissions
function to use this technique.
This time, the output is as we first intended. By setting the default argument item=instruction
, we force Python to evaluate instruction
and bind its current value to the item
parameter for each new function. Each closure now holds its own unique copy of the instruction, rather than sharing a single reference.
Part 4: The Anonymous Calligrapher
This principle of late binding also applies to lambda functions. As we learnt in Koan 5, lambdas are simply anonymous functions. The "lazy calligrapher" effect is very common when lambdas are created in a loop.
Here is the original problem, written with a lambda:
Just like with the def
-defined function, this will also return "cloud," because the lambda closes over the instruction
variable, not its value.
To correct this, we use a default argument for the lambda function:
We can make this more concise and pythonic by using list comprehension to create all the commissions in a single stroke.
Part 5: The Master's Scrupulous Note
What if we could not use a default argument? Is there another way to force the calligrapher to capture the instruction immediately? The solution lies in creating a new scope for each instruction, forcing each closure to "listen" to a unique voice.
We can achieve this by creating a factory function that takes the instruction as an argument, thus creating a new, isolated environment for each closure.
This code also yields the correct output. On each iteration of the loop, we call create_executor(instruction)
. This function is given the current value of instruction
. It then creates a new closure, execute_commission
, which closes over item
. Because item
is a local variable within create_executor
, its value is isolated from the outer loop's changes (as we learnt in Koan 4 about Python’s LEGB rule). Each call to create_executor
creates a new, private memory for the closure it returns. It is as if the master wrote a small, specific note for each drawing, which the calligrapher could not forget.
Part 6: The Customized Brush
There are other, more concise or functional ways to achieve the same result. The choice of method often comes down to the clarity and style you prefer. We can use functools.partial
to "pre-bind" the argument to a function. This is like preparing a special brush for each task, with the instruction already etched into its handle.
The Final Brushstroke
The Master's teaching was simple: the calligrapher did not remember what was once said, only the command that was last spoken when the drawing finally began.
In your code, your functions are like the forgetful calligrapher. The closure does not remember the values the variable held when it was created, only the last value is recalled when the function is run.