0

Python is not fixing the value of the loop variable when passing it to a lambda.

xs = ["a", "b", "c"]
print_statements: List[Callable[[], None]] = []
for x in xs:
    print_statements.append(lambda: print(f"The value: {x}"))

for print_statement in print_statements:
    print_statement()

// Output:
// The value: c
// The value: c
// The value: c

Consider the same example in Java:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args)
    {
        List<String> xs = List.of("a", "b", "c");
        List<Runnable> printStatements = new ArrayList<>();
        for (String x : xs)
        {
            printStatements.add(() -> System.out.println("The value: " + x));
        }

        for (Runnable printStatement : printStatements)
        {
            printStatement.run();
        }
    }
}

// Output:
// The value: a
// The value: b
// The value: c

I have more experience in Java. Thus, the output of the Java program is what I have expected. This is a real gotcha for me in Python and I would like to understand what is happening.

  • What is going on in the Python example?
    • It seems like the reference to the loop variable is passed
    • Is there maybe lazy evaluation going on, which causes this behavior?
  • Why has Python been implemented this way? What is the reasoning behind it by the Python devs?
  • Could you point me to Python documentation that covers the relevant scoping rules?
  • How can I change above Python Code to get the same output as in the Java example
mihca
  • 997
  • 1
  • 10
  • 29

1 Answers1

1

How can I change above Python Code to get the same output as in the Java example

One can bind the value of the outer scope variable to the scope of the lambda:

xs = ["a", "b", "c"]
print_statements: List[Callable[[], None]] = []
for x in xs:
    print_statements.append(lambda local_x=x: print(f"The value: {local_x}"))

# Output:
# The value: a
# The value: b
# The value: c

Still I wonder why Python has been designed this way.


I just noticed that Java will not let you do the following:

for (int i = 0; i < xs.size(); i++)
{
    printStatements.add(() -> System.out.println("The value: " + xs.get(i)));
}

The compiler complaints: Variable used in lambda expression should be final or effectively final

A fix for this is then

for (int i = 0; i < xs.size(); i++)
{
    int finalI = i;
    printStatements.add(() -> System.out.println("The value: " + xs.get(finalI)));
}

I think this corresponds to the Python code. Python is less strict and allows (in Java terms) to use a variable that is not "final or effectively final" to be used in a lambda.

mihca
  • 997
  • 1
  • 10
  • 29