How to capture a value for later in a nested function?

Viewed 233
def make_functions():
    flist = []

    for i in [1, 2, 3]:
        def print_i():
            print(i)
        flist.append(print_i)

    return flist

functions = make_functions()
for f in functions:
    f()

The output of this code is:

3
3
3

Questions:

  1. Why does print_i only capture the final value of i?
  2. How can we modify this code to make it print 1, 2, 3?

One way to answer question #2 is as follows, but I'm wondering if there's a more elegant way that captures the value of i.

def make_functions():
    flist = []

    for i in [1, 2, 3]:
        def print_i(j):
            print(j)
        flist.append((print_i, i))

    return flist

functions = make_functions()
for f, i in functions:
    f(i)
2 Answers
  1. Like the user above said, the scope of i in your case is of make_function. So when the functions are executed, they print the value of i in that scope, 3. This is because the function is saved with the body print(i) without any concept of what i is at that moment. i is not evaluated until the function is called later on, when i is already 3.

  2. To answer question 2, we need to get i in the scope of the print_i function. We can do this with a wrapper function that has i as an argument, as the user above did, or much simpler, we can just pass the value of i as a default value. Now we are appending 3 different print(i) functions, each with its own default value, which is the value of i at that moment in the for loop.

def make_functions():
    flist = []

    for i in [1, 2, 3]:
        def print_i(i=i):
            print(i)
        flist.append(print_i)

    return flist

functions = make_functions()
for f in functions:
    f()

Because scope of i in your case is of make_functions, you should make a separated scope for it, here I wrap it into a function

def make_functions():
    flist = []

    for i in [1, 2, 3]:
        def print_i_factory(i):
            def print_i():
                print(i)

            return print_i

        flist.append(print_i_factory(i))

    return flist

functions = make_functions()
for f in functions:
    f()

Related