Reputation: 4780
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
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
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