28

I just ran across Eric Lippert's Closing over the loop variable considered harmful via SO, and, after experimenting, realized that the same problem exists (and is even harder to get around) in Python.

>>> l = []
>>> for r in range(10):
...   def foo():
...      return r
...   l.append(foo)
...
>>> for f in l:
...   f()
...
9
9
9
# etc

and, the standard C# workaround doesn't work (I assume because of the nature of references in Python)

>>> l = []
>>> for r in range(10):
...   r2 = r
...   def foo():
...      return r2
...   l.append(foo)
...
>>> for f in l:
...   f()
...
9
9
9
# etc

I recognize that this isn't much of a problem in Python with its general emphasis on non-closure object structures, but I'm curious if there is an obvious Pythonic way to handle this, or do we have to go the JS route of nested function calls to create actually new vars?

>>> l = []
>>> for r in range(10):
...     l.append((lambda x: lambda: x)(r))
...
>>> for f in l:
...     f()
...
0
1
2
# etc
McKay
  • 12,334
  • 7
  • 53
  • 76
quodlibetor
  • 8,185
  • 4
  • 35
  • 48

1 Answers1

32

One way is to use a parameter with default value:

l = []
for r in range(10):
    def foo(r = r):
        return r
    l.append(foo)

for f in l:
    print(f())

yields

0
1
2
3
4
5
6
7
8
9

This works because it defines an r in foo's local scope, and binds the default value to it at the time foo is defined.


Another way is to use a function factory:

l = []
for r in range(10):
    def make_foo(r):
        def foo():
            return r
        return foo
    l.append(make_foo(r))

for f in l:
    print(f())

This works because it defines r in make_foo's local scope, and binds a value to it when make_foo(r) is called. Later, when f() is called, r is looked-up using the LEGB rule. Although r is not found in the local scope of foo, it is found in the enclosing scope of make_foo.

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 1
    Oh, I like that default argument trick. That function factory is semantically similar to the double lambda thing at the end of mine. Lambda factories are the bane of readability, though. – quodlibetor Jan 20 '12 at 20:19
  • Neither of these close over the variable. Not useful for updating. – McKay Mar 15 '18 at 19:48
  • @McKay Exactly, the question is about how *not* to close over the variable, but to save a different value each time through the loop. – mhsmith Apr 08 '20 at 17:27
  • @mhsmith Yeah, looking back on this old post, I agree with you today. I have no idea what I was thinking 2 years ago. Maybe I was trying to edit the loop variable? – McKay Apr 10 '20 at 17:20