Date | Author | Version | Note |
---|---|---|---|
2024.02.02 | Dog Tao | V1.0 | Release as V1.0 |
Introduction
When dynamically creating functions within a loop in Python, if each function is defined to use a variable that changes in each iteration of the loop (like the loop variable itself), all functions might end up behaving like the last function created. This happens due to a common pitfall known as the “late binding closure” issue. Essentially, the functions capture and share the same variable, not its value at the time of the function’s definition, but its final value after the loop has completed.
Why does this happen?
In Python, closures—functions defined inside another function—capture variables by reference, not by value. This means that a function defined inside a loop captures a reference to the loop variable, not a snapshot of its value at the time the function is defined. When the loop variable changes, the change is reflected in all functions that reference this variable, making it seem as if all functions are using the last value of the loop variable.
Example Illustration
Consider the following example where we create a list of functions in a loop, each supposed to print a different number:
functions = []
for i in range(3):
functions.append(lambda: print(i))
for f in functions:
f() # Expecting to print 0, 1, 2 but prints 2, 2, 2
All functions print 2
, the last value of i
, because they all reference the same i
variable, which ends up being 2
after the loop ends.
How to Fix This
To avoid this issue, you can define your functions using default arguments, which capture the value at the time the function is defined, not the reference:
functions = []
for i in range(3):
functions.append(lambda i=i: print(i)) # Using default arguments to capture the current value of i
for f in functions:
f() # This will correctly print 0, 1, 2
Another common approach is to use a function factory that generates functions, capturing the current state of variables:
def make_function(i):
return lambda: print(i)
functions = []
for i in range(3):
functions.append(make_function(i)) # Using a factory function to capture the current value of i
for f in functions:
f() # This will correctly print 0, 1, 2
Both of these solutions ensure that the value of i
at the time of each function’s creation is captured and used, rather than the final value of i
after the loop has finished.