2

I'm at a loss here. I'm trying to define a function inside a for loop. The function uses a variable defined inside the loop and could look something similar like that :

myFuns = []
for i in range(10):
    j = i + 4
    def fun(): print(j)
    myFuns += [fun]

At the end of this loop, my functions in myFuns are differents, but they do the same thing, as apparently, this is not a new variable j that is passed to fun(), but the reference of j.

I'd be very glad to know how to pass the value of j and not just the reference.

I didn't know that a variable created in a loop could outlived the loop, so this is new territory for me here...

ogr
  • 610
  • 7
  • 23
  • why can't you just do `myFuns.append(j)` – Hippolippo Mar 01 '19 at 15:17
  • 1
    It is related to this concept called *closure*. They just *refer* to the `j` from the function it was created. See this https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example if you don't mind a bit of JS. – Nishant Mar 01 '19 at 15:19
  • Check this out https://docs.python-guide.org/writing/gotchas/#late-binding-closures – bereal Mar 01 '19 at 15:20
  • There are a limited number of scopes in Python; see https://stackoverflow.com/a/292502/1126841. "Blocks" induced by a loop or other compound statement do *not* create new scopes. – chepner Mar 01 '19 at 15:24

1 Answers1

6

j is a free variable, whose value is looked up when fun is called; fun does not bake the current value of j into its definition. Further, lacking any other containing scope, the call to fun will refer to j in the global scope, where its last set value was its value in the last iteration of the loop.

To actually store a fixed value for j in fun when it is defined, you would need to pass it as a parameter with a default value:

myFuns = []
for i in range(10):
    j = i + 4
    def fun(j=j):
        print(j)
    myFuns.append(fun)  # Don't use += [...] for a single item

However, a closure may be more appropriate:

# Here, j is a free variable in _, but its value comes from the local
# variable j defined by make_fun, not the global scope.
def make_fun(j):
    def _():
        return j
    return _

myFuns = [make_fun(i) for i in range(10)]
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks. It's pretty weird, I'll need some time to wrap my head around this I think. – ogr Mar 01 '19 at 15:38