|
5 | 5 |
|
6 | 6 | <overview>
|
7 | 7 | <p>
|
8 |
| -Nested functions are a useful feature of Python as it allows a function to access the variables of its enclosing function. |
9 |
| -However, the programmer needs to be aware that when an inner function accesses a variable in an outer scope, |
10 |
| -it is the variable that is captured, not the value of that variable. |
| 8 | +In Python, a nested function or lambda expression that captures a variable from its surrounding scope is a <em>late-binding</em> closure, |
| 9 | +meaning that the value of the variable is determined when the closure is called, not when it is created. |
11 | 10 | </p>
|
12 | 11 | <p>
|
13 |
| -Therefore, care must be taken when the captured variable is a loop variable, since it is the loop <em>variable</em> and |
14 |
| -<em>not</em> the <em>value</em> of that variable that is captured. |
15 |
| -This will mean that by the time that the inner function executes, |
16 |
| -the loop variable will have its final value, not the value when the inner function was created. |
| 12 | +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. |
17 | 13 | </p>
|
18 | 14 |
|
19 | 15 | </overview>
|
20 | 16 | <recommendation>
|
21 | 17 | <p>
|
22 |
| -The simplest way to fix this problem is to add a local variable of the same name as the outer variable and initialize that |
23 |
| -using the outer variable as a default. |
24 |
| -<code> |
25 |
| -for var in seq: |
26 |
| - ... |
27 |
| - def inner_func(arg): |
28 |
| - ... |
29 |
| - use(var) |
30 |
| -</code> |
31 |
| -becomes |
32 |
| -<code> |
33 |
| -for var in seq: |
34 |
| - ... |
35 |
| - def inner_func(arg, var=var): |
36 |
| - ... |
37 |
| - use(var) |
38 |
| -</code> |
| 18 | +Ensure that closures that capture loop variables aren't used outside of a single iteration of the loop. |
| 19 | +To capture the value of a loop variable at the time the closure is created, use a default parameter, or <code>functools.partial</code>. |
39 | 20 | </p>
|
40 | 21 |
|
41 | 22 | </recommendation>
|
42 | 23 | <example>
|
43 | 24 | <p>
|
44 |
| -In this example, a list of functions is created which should each increment its argument by its index in the list. |
45 |
| -However, since <code>i</code> will be 9 when the functions execute, they will each increment their argument by 9. |
| 25 | +In the following (BAD) example, a `tasks` list is created, but each task captures the loop variable <code>i</code>, and reads the same value when run. |
46 | 26 | </p>
|
47 |
| -<sample src="LoopVariableCapture.py" /> |
| 27 | +<sample src="examples/bad.py" /> |
48 | 28 | <p>
|
49 |
| -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. |
| 29 | +In the following (GOOD) example, each closure has an `i` default parameter, shadowing the outer <code>i</code> variable, the default value of which is determined as the value of the loop variable <code>i</code> at the time the closure is created. |
| 30 | +<sample src="examples/good.py" /> |
| 31 | +In the following (GOOD) example, <code>functools.partial</code> is used to partially evaluate the lambda expression with the value of <code>i</code>. |
| 32 | +<sample src="examples/good2.py" /> |
50 | 33 | </p>
|
51 | 34 |
|
52 |
| -<sample src="LoopVariableCapture2.py" /> |
53 | 35 |
|
54 | 36 | </example>
|
55 | 37 | <references>
|
56 |
| -<li>The Hitchhiker’s Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a></li> |
57 |
| -<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a></li> |
| 38 | +<li>The Hitchhiker's Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a>.</li> |
| 39 | +<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a>.</li> |
| 40 | +<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3431676/creating-functions-or-lambdas-in-a-loop-or-comprehension">Creating functions (or lambdas) in a loop (or comprehension)</a>.</li> |
| 41 | +<li>Python Language Reference: <a href="https://docs.python.org/3/library/functools.html#functools.partial">functools.partial</a>.</li> |
58 | 42 |
|
59 | 43 | </references>
|
60 | 44 | </qhelp>
|
0 commit comments