SCR120: dangerous-loop-closure
Function defined in a loop uses the loop's variable
| Code | SCR120 |
| Name | dangerous-loop-closure |
| Category | Scripts (SCR) |
| Default severity | warning |
| Enabled by default | no |
| Fix available | no |
| ign-lint equivalent | none |
What it does
Section titled “What it does”Reports names read by a lambda or nested def created inside a for
loop when the name is a variable the loop rebinds (its target, a name
assigned in its body or the target of an except clause in it), and
names read by a lambda created in a comprehension when the name is one
of its variables (a list comprehension binds them in the enclosing
scope, the other kinds in their own; either way they are rebound on
every iteration).
Generator expressions created in the loop count too: one reads the loop's variables when it is consumed (in its body and its conditions; its first iterable is evaluated where it is written).
Not reported: a function that cannot outlive the iteration that creates
it, so that it reads that iteration's value: a lambda called where it
is written ((lambda: u)()), a lambda passed directly to the builtin
sorted, min, max, map, filter or reduce as an argument the
builtin calls while it runs (the cmp and key of sorted, the key
of min and max, the first argument of map, filter and reduce:
[sorted(rows, key=lambda r: r[c]) for c in cols]; any other argument
is a value the builtin may return, max(lambda: t, lambda: t) returns a
lambda), and a def (or a name bound to a lambda) whose every use is
a call later in the same block of the loop body, in the same scope. A
use before the definition, any other use (stored, passed, returned), a
decorator, a call from a nested function or a closure it returns still
count. So does a generator: calling a generator function (a def or
lambda with yield) runs none of its body, and a generator expression
runs when it is consumed, unless it is consumed where it is written: as
the iterable of a builtin that consumes it (sum, any, all, list,
tuple, set, dict, sorted, a single argument of min or max
(not with *args that may add another: max(g, *rest) may return g;
the items of a literal *args are arguments at their positions,
max(*(g,)) consumes g), the sequences of map, filter, reduce and zip, ''.join), of a
for statement or of a list, set or dict comprehension. A generator
function whose every call is consumed that way (total += sum(h()))
is not reported either.
Why is this bad?
Section titled “Why is this bad?”The function reads the variable when it is called, not when it is
created. Functions called after the loop (callbacks given to
system.util.invokeLater, handlers stored in a dict) all see the last
value.
Example
Section titled “Example”for path in paths: system.util.invokeLater(lambda: system.tag.writeBlocking([path], [0]))Use instead:
for path in paths: system.util.invokeLater(lambda path=path: system.tag.writeBlocking([path], [0]))The same in a comprehension (Jython: [2, 2, 2]):
handlers = [lambda: k for k in range(3)]pylint
Section titled “pylint”Corresponds to W0640 cell-var-from-loop.
Generated from the rule metadata by iglint generate-docs; iglint rule SCR120 prints the same page in the terminal. Source: docs/rules/SCR120.md.