3
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)
Abhijit Sarkar
  • 16,021
  • 13
  • 78
  • 152

2 Answers2

3
  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()
devdev_dev
  • 358
  • 2
  • 15
2

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()

hgb123
  • 9,840
  • 3
  • 11
  • 31
  • This makes sense, but if scope of `i` is `make_functions`, how does the code print the last value of `i` (question 1)? – Abhijit Sarkar Sep 23 '20 at 07:44
  • 1
    @AbhijitSarkar according to [this](https://docs.python.org/dev/reference/compound_stmts.html#the-for-statement), `for-loop makes assignments to the variables in the target list. This overwrites all previous assignments`, so every `i` in `print_i` of question 1 is referenced to that overwritten value – hgb123 Sep 23 '20 at 07:49
  • Well, that's fine, but as I said in the comment to the other answer, `i` is a local variable in `make_functions`, and shouldn't be available inside the `for` loop after exiting `make_functions`. – Abhijit Sarkar Sep 23 '20 at 07:51
  • @AbhijitSarkar [this](https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops) may answer your question – hgb123 Sep 23 '20 at 07:55
  • Specifically, "_Previous proposals to make for-loop variables local to the loop have stumbled on the problem of existing code that relies on the loop variable keeping its value after exiting the loop, and it seems that this is regarded as a desirable feature_". I hate when people play the backward compatibility card and developers bend over backwards to keep doing what's clearly wrong. – Abhijit Sarkar Sep 23 '20 at 07:58