"Learning Python, 4th Ed." mentions that:
the enclosing scope variable is looked up when the nested functions
are later called..
However, I thought that when a function exits, all of its local references disappear.
def makeActions():
acts = []
for i in range(5): # Tries to remember each i
acts.append(lambda x: i ** x) # All remember same last i!
return acts
makeActions()[n] is the same for every n because the variable i is somehow looked up at call time. How does Python look up this variable? Shouldn’t it not exist at all because makeActions has already exited? Why doesn’t Python do what the code intuitively suggests, and define each function by replacing i with its current value within the for loop as the loop is running?
I think it’s pretty obvious what happens when you think of
ias a name not some sort of value. Your lambda function does something like “take x: look up the value of i, calculate i**x” … so when you actually run the function, it looks upijust then soiis4.You can also use the current number, but you have to make Python bind it to another name:
It might seem confusing, because you often get taught that a variable and it’s value are the same thing — which is true, but only in languages that actually use variables. Python has no variables, but names instead.
About your comment, actually i can illustrate the point a bit better:
You said you changed i to 6, that is not what actually happend:
i=6means “i have a value,6and i want to name iti“. The fact that you already usedias a name matters nothing to Python, it will just reassign the name, not change it’s value (that only works with variables).You could say that in
myList = [i, i, i], whatever valueicurrently points to (the number 5) gets three new names:mylist[0], mylist[1], mylist[2]. That’s the same thing that happens when you call a function: The arguments are given new names. But that is probably going against any intuition about lists …This can explain the behavior in the example: You assign
mylist[0]=5,mylist[1]=5,mylist[2]=5– no wonder they don’t change when you reassign thei. Ifiwas something muteable, for example a list, then changingiwould reflect on all entries inmyListtoo, because you just have different names for the same value!The simple fact that you can use
mylist[0]on the left hand of a=proves that it is indeed a name. I like to call=the assign name operator: It takes a name on the left, and a expression on the right, then evaluates the expression (call function, look up the values behind names) until it has a value and finally gives the name to the value. It does not change anything.For Marks comment about compiling functions:
Well, references (and pointers) only make sense when we have some sort of addressable memory. The values are stored somewhere in memory and references lead you that place. Using a reference means going to that place in memory and doing something with it. The problem is that none of these concepts are used by Python!
The Python VM has no concept of memory – values float somewhere in space and names are little tags connected to them (by a little red string). Names and values exist in separate worlds!
This makes a big difference when you compile a function. If you have references, you know the memory location of the object you refer to. Then you can simply replace then reference with this location.
Names on the other hand have no location, so what you have to do (during runtime) is follow that little red string and use whatever is on the other end. That is the way Python compiles functions: Where
ever there is a name in the code, it adds a instruction that will figure out what that name stands for.
So basically Python does fully compile functions, but names are compiled as lookups in the nesting namespaces, not as some sort of reference to memory.
When you use a name, the Python compiler will try to figure out where to which namespace it belongs to. This results in a instruction to load that name from the namespace it found.
Which brings you back to your original problem: In
lambda x:x**i, theiis compiled as a lookup in themakeActionsnamespace (becauseiwas used there). Python has no idea, nor does it care about the value behind it (it does not even have to be a valid name). One that code runs theigets looked up in it’s original namespace and gives the more or less expected value.