cell-var-from-loop / W0640#

Message emitted:

Cell variable %s defined in loop

Description:

A variable used in a closure is defined in a loop. This will result in all closures using the same value for the closed-over variable.

Problematic code:

def teacher_greeting(names):
    greetings = []
    for name in names:

        def greet():
            # do something
            print(f"Hello, {name}!")  # [cell-var-from-loop]

        if name.isalpha():
            greetings.append(greet)

    for greet in greetings:
        # the "name" variable is evaluated when the function is called here,
        # which is the last value it had in the loop - "Not-A-Name"
        greet()


teacher_greeting(["Graham", "John", "Terry", "Eric", "Terry", "Michael"])
# "Hello, Michael!"
# "Hello, Michael!"
# "Hello, Michael!"
# "Hello, Michael!"
# "Hello, Michael!"

Correct code:

functools.partial.py:

import functools


def teacher_greeting(names):
    greetings = []
    for name in names:
        if name.isalpha():
            # "name" is evaluated when the partial is created here, so this
            # does not do lazy evaluation
            greetings.append(functools.partial(print, f"Hello, {name}!"))

    for greet in greetings:
        # `partial`s are called like functions, but you've already passed the
        # arguments to them
        greet()


teacher_greeting(["Graham", "John", "Terry", "Eric", "Terry", "Michael"])
# "Hello, Graham!"
# "Hello, John!"
# "Hello, Eric!"
# "Hello, Terry!"
# "Hello, Michael!"

new_function.py:

def teacher_greeting(names):
    def greet(name):
        # do something
        print(f"Hello, {name}!")

    for name in names:
        if name.isalpha():
            # we're passing the value of "name" to the function here
            greet(name)


teacher_greeting(["Graham", "John", "Terry", "Eric", "Terry", "Michael"])
# "Hello, Graham!"
# "Hello, John!"
# "Hello, Eric!"
# "Hello, Terry!"
# "Hello, Michael!"

Related links:

Created by the variables checker.