What do you think this Python code will print? Go!

By Google for Developers

Share:

Key Concepts:

  • Lambda functions
  • Closures
  • Scope
  • Late binding

Lambda Functions in a Loop: A Python Brain Teaser

This YouTube video presents a Python code snippet involving the creation of multiple lambda functions within a loop and poses a question about the expected output. The core of the teaser lies in understanding how lambda functions, closures, and variable scope interact in Python, particularly when defined inside a loop.

The Code Snippet and the Puzzle

The presented code is as follows:

funcs = []
for i in range(5):
    funcs.append(lambda: i)

for f in funcs:
    print(f())

The question posed is: "What do you think this will print?" The presenter notes that this can be a surprising outcome even for experienced Python developers.

Explanation of the Behavior: Late Binding and Closures

The surprising output arises from the concept of late binding in Python. When a lambda function is defined, it doesn't immediately capture the value of the variable i from the loop. Instead, it creates a reference to the variable i itself.

  • Lambda Functions: These are small, anonymous functions defined with the lambda keyword. They can take any number of arguments but can only have one expression.
  • Closures: A closure is a function that "remembers" the environment in which it was created, even after the outer function has finished executing. In this case, each lambda function is a closure that "closes over" the variable i.
  • Scope: The variable i exists in the scope of the for loop.

When the loop finishes, the value of i is 4 (since range(5) goes from 0 to 4). When each lambda function f() is called later, it looks up the current value of i in its enclosing scope. Because all lambda functions were created within the same loop iteration and refer to the same i variable, they all end up referencing the final value of i after the loop has completed.

The Expected Output vs. The Actual Output

  • Intuitive (but incorrect) expectation: Many might expect the output to be 0, 1, 2, 3, 4, as they might assume each lambda function captures the value of i at the time of its creation.
  • Actual Output: The code will print 4 five times.

Supporting Evidence and Reasoning

The presenter's explanation hinges on the fact that the lambda function's expression (i) is evaluated when the function is called, not when it is defined. This is a common characteristic of closures in many programming languages, including Python.

Addressing the Problem: Capturing the Value

To achieve the expected output (0, 1, 2, 3, 4), the lambda function needs to capture the value of i at the time of its creation. This can be done by passing i as a default argument to the lambda function. Default arguments are evaluated when the function is defined.

Here's the corrected code:

funcs = []
for i in range(5):
    funcs.append(lambda x=i: x) # x=i captures the value of i at definition time

for f in funcs:
    print(f())

In this modified version, lambda x=i: x creates a lambda function where x is a default argument initialized with the current value of i. When the lambda is called, it uses this captured value.

Conclusion and Takeaways

The primary takeaway from this brain teaser is the importance of understanding late binding and how variables are referenced in closures. When creating functions within loops that need to capture specific values from the loop's iteration, it's crucial to ensure those values are captured at definition time, often by using default arguments or other techniques to create distinct copies of the values. This behavior can be a subtle but significant source of bugs in Python code.

Chat with this Video

AI-Powered

Load the transcript when you're ready to chat so the initial page stays lighter.

Ready to summarize another video?

Summarize YouTube Video