-Nested functions are a useful feature of Python as it allows a function to access the variables of its enclosing function. -However, the programmer needs to be aware that when an inner function accesses a variable in an outer scope, -it is the variable that is captured, not the value of that variable. -
--Therefore, care must be taken when the captured variable is a loop variable, since it is the loop variable and -not the value of that variable that is captured. -This will mean that by the time that the inner function executes, -the loop variable will have its final value, not the value when the inner function was created. -
- -
-The simplest way to fix this problem is to add a local variable of the same name as the outer variable and initialize that
-using the outer variable as a default.
-
-for var in seq:
- ...
- def inner_func(arg):
- ...
- use(var)
-
-becomes
-
-for var in seq:
- ...
- def inner_func(arg, var=var):
- ...
- use(var)
-
-
-In this example, a list of functions is created which should each increment its argument by its index in the list.
-However, since i
will be 9 when the functions execute, they will each increment their argument by 9.
-
-This can be fixed by adding the default value as shown below. The default value is computed when the function is created, so the desired effect is achieved. -
- -+In Python, a nested function or lambda expression that captures a variable from its surrounding scope is a late-binding closure, +meaning that the value of the variable is determined when the closure is called, not when it is created. +
++Care must be taken when the captured variable is a loop variable. If the closure is called after the loop ends, it will use the value of the variable on the last iteration of the loop, rather than the value at the iteration at which it was created. +
+ +
+Ensure that closures that capture loop variables aren't used outside of a single iteration of the loop.
+To capture the value of a loop variable at the time the closure is created, use a default parameter, or functools.partial
.
+
+In the following (BAD) example, a tasks
list is created, but each task captures the loop variable i
, and reads the same value when run.
+
+In the following (GOOD) example, each closure has an i
default parameter, shadowing the outer i
variable, the default value of which is determined as the value of the loop variable i
at the time the closure is created.
+
+In the following (GOOD) example, functools.partial
is used to partially evaluate the lambda expression with the value of i
.
+