Julian
Julian

Reputation: 4780

Variable binding time in Python closures

From Python's Programming FAQ, this doesn't work as many people expect:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)
>>> squares[2]() # outputs 16, not 4

The following explanation is offered:

This happens because x is not local to the lambdas, but is defined in the outer scope, and it is accessed when the lambda is called --- not when it is defined.

This is fine. However, the explanation does not sound entirely correct to me. Consider this:

def buildFunction():
    myNumber = 6 # Won't compile without this line
    def fun():
        return myNumber
    return fun

def main():
    myNumber = 3 # Ignored
    myFun = buildFunction()
    myNumber = 3 # Ignored
    print(myFun()) # prints 6, not 3

This makes sense, but can someone please offer a more accurate/general definition of when variable binding of Python closures occurs?

Upvotes: 1

Views: 588

Answers (2)

steviestickman
steviestickman

Reputation: 1251

Every variable has a scope. Name lookup (getting the value that the name points to) resolves by default to the most inner scope. You can only override names in local scope or a containing scope(using the keywords nonlocal and global). In your second example in main you assign myNumber to a different scope that your fun function can access.

squares = []
 for x in range(5):
    squares.append(lambda: x**2) # scope = global / lambda
print(squares[2]())

def buildFunction():
    myNumber = 6 # scope = global / buildFunction
    def fun():
        return myNumber # scope = global / buildFunction / fun
    return fun

myNumber = 1 # scope = global
def main():
    myNumber = 3 # scope = global / main. Out of scope for global / buildFunction
    myFun = buildFunction()
    print(myFun())

When fun is called, Python looks at fun's local scope and can't find myNumber. So it looks at its containing scope. The buildFunction scope has myNumber in scope (myNumber = 6) so fun returns 6.

If buildFunction did not have myNumber in scope then Python looks at the next scope. In my version the global scope has myNumber = 1 So fun would return 1.

If myNumber also does not exist in the global scope, a NameError is raised because myNumber could not be found in local scope nor any of its containing scopes.

Upvotes: 1

FiddleStix
FiddleStix

Reputation: 3721

Consider this:

def main():
    def buildFunction():
        def fun():
            return myNumber

        return fun

    myNumber = 3  # can use this...
    myFun = buildFunction()
    myNumber = 3  # ... or this
    print(myFun())  # prints 3 this time

This is more comparable to the lambda example because the closure function is nested within the scope that declares the variable of interest. Your example had two different scopes so had two, completely unrelated, myNumbers.

In case you haven't come across the nonlocal keyword, can you guess what this will print?

def main():
    def buildFunction():
        nonlocal myNumber
        myNumber = 6

        def fun():
            return myNumber

        return fun

    myNumber = 3
    myFun = buildFunction()
    # myNumber = 3
    print(myFun())

Upvotes: 2

Related Questions