Why do different variable names get different results(python2.7)?
Question:
in this code:
results = []
for i in [1, 2, 3, 4]:
def inner(y):
return i
results.append(inner)
for i in results:
print i(None)
the output is “function inner at 0x107dea668”
if i change i to other letter, for example:
results = []
for i in [1, 2, 3, 4]:
def inner(y):
return i
results.append(inner)
for j in results:
print j(None)
the output is “4”
Answer
results = []
for i in [1, 2, 3, 4]:
def inner(y):
print "in inner:%s " % id(i)
return i
results.append(inner)
# i -> 4
for i in results:
# i -> func inner
print "i: %s" % i
print "in loop: %s " % id(i)
# func inner <===> A
# i == A -> return i -> return A, so when call funtion inner, will return itself
# print "call: %s" % i(None)
print "call: %s" % i(None)(None)(None)
print "------------------------------"
i: function inner at 0x101344d70
in loop: 4315172208
in inner:4315172208
in inner:4315172208
in inner:4315172208
call: function inner at 0x101344d70
i: function inner at 0x101344de8
in loop: 4315172328
in inner:4315172328
in inner:4315172328
in inner:4315172328
call: function inner at 0x101344de8
i: function inner at 0x101344e60
in loop: 4315172448
in inner:4315172448
in inner:4315172448
in inner:4315172448
call: function inner at 0x101344e60
i: function inner at 0x101344ed8
in loop: 4315172568
in inner:4315172568
in inner:4315172568
in inner:4315172568
call: function inner at 0x101344ed8
Answers:
Actually, when you use j in results, the variable i remains 4, due to after the first-loop, the i stays number 4;
But if you use i in results, i refer to functions stored inside of results. The i is not fixed until you start to call it. as @Jean-François Fabre said, lazy evaluation.
Additionally, if you wanna inner function to store each i when loop from 1-4, you should store i in the related scope, use partial function;
from functools import partial
results = []
for i in [1, 2, 3, 4]:
def inner(i, y):
return i
results.append(partial(inner, i))
for i in results:
print(i(None))
this will give you results of
1
2
3
4
which is more meaningful.
The inner
function you have defined contains a free variable referring to the global variable i
. This is perhaps clearer in an example like this:
def inner(y):
return i
i = 1
print inner(None)
i = 2
print inner(None)
which prints 1 and then 2
In your first example, at the time of the call to inner
, i
has the value which is the function and so that is what is printed when i
(which is inner
) is called.
In the second example, at the time of the call to inner
, i
has the value 4 and so that is what is printed when j
(which is inner
) is called.
A clear way to express what you presumably wanted here is to use a partially evaluated function, as recommended in another answer. Another way is to use an enclosing function to create a closure. Like this:
results = []
for i in [1, 2, 3, 4]:
def outer(k):
def inner(y):
return k
return inner
results.append(outer(i))
for i in results:
print i(None)
which will print 1 to 4 as presumably you want.
A little trick sometimes used in Python is to use the default value of a variable as a cell to contain a value:
results = []
for i in [1, 2, 3, 4]:
def inner(y, i = i):
return i
results.append(inner)
for i in results:
print i(None)
which also prints 1 to 4.
Variable i
returned in the inner
function keeps its value from the last context it was assigned.
If you debug the code, using a breakpoint inside inner
function, picture will become clearer if select the previous frame/context (bottom left in figures) before calling the function.
When you use i
, it is assigned inside the second for
, so it will have a function as its value (highlighted in yellow in Figure 1).
Now, if you use j
, variable i
will keep its last value from the previous context: the for
over the list (Figure 2).
in this code:
results = []
for i in [1, 2, 3, 4]:
def inner(y):
return i
results.append(inner)
for i in results:
print i(None)
the output is “function inner at 0x107dea668”
if i change i to other letter, for example:
results = []
for i in [1, 2, 3, 4]:
def inner(y):
return i
results.append(inner)
for j in results:
print j(None)
the output is “4”
Answer
results = []
for i in [1, 2, 3, 4]:
def inner(y):
print "in inner:%s " % id(i)
return i
results.append(inner)
# i -> 4
for i in results:
# i -> func inner
print "i: %s" % i
print "in loop: %s " % id(i)
# func inner <===> A
# i == A -> return i -> return A, so when call funtion inner, will return itself
# print "call: %s" % i(None)
print "call: %s" % i(None)(None)(None)
print "------------------------------"
i: function inner at 0x101344d70
in loop: 4315172208
in inner:4315172208
in inner:4315172208
in inner:4315172208
call: function inner at 0x101344d70
i: function inner at 0x101344de8
in loop: 4315172328
in inner:4315172328
in inner:4315172328
in inner:4315172328
call: function inner at 0x101344de8
i: function inner at 0x101344e60
in loop: 4315172448
in inner:4315172448
in inner:4315172448
in inner:4315172448
call: function inner at 0x101344e60
i: function inner at 0x101344ed8
in loop: 4315172568
in inner:4315172568
in inner:4315172568
in inner:4315172568
call: function inner at 0x101344ed8
Actually, when you use j in results, the variable i remains 4, due to after the first-loop, the i stays number 4;
But if you use i in results, i refer to functions stored inside of results. The i is not fixed until you start to call it. as @Jean-François Fabre said, lazy evaluation.
Additionally, if you wanna inner function to store each i when loop from 1-4, you should store i in the related scope, use partial function;
from functools import partial
results = []
for i in [1, 2, 3, 4]:
def inner(i, y):
return i
results.append(partial(inner, i))
for i in results:
print(i(None))
this will give you results of
1
2
3
4
which is more meaningful.
The inner
function you have defined contains a free variable referring to the global variable i
. This is perhaps clearer in an example like this:
def inner(y):
return i
i = 1
print inner(None)
i = 2
print inner(None)
which prints 1 and then 2
In your first example, at the time of the call to inner
, i
has the value which is the function and so that is what is printed when i
(which is inner
) is called.
In the second example, at the time of the call to inner
, i
has the value 4 and so that is what is printed when j
(which is inner
) is called.
A clear way to express what you presumably wanted here is to use a partially evaluated function, as recommended in another answer. Another way is to use an enclosing function to create a closure. Like this:
results = []
for i in [1, 2, 3, 4]:
def outer(k):
def inner(y):
return k
return inner
results.append(outer(i))
for i in results:
print i(None)
which will print 1 to 4 as presumably you want.
A little trick sometimes used in Python is to use the default value of a variable as a cell to contain a value:
results = []
for i in [1, 2, 3, 4]:
def inner(y, i = i):
return i
results.append(inner)
for i in results:
print i(None)
which also prints 1 to 4.
Variable i
returned in the inner
function keeps its value from the last context it was assigned.
If you debug the code, using a breakpoint inside inner
function, picture will become clearer if select the previous frame/context (bottom left in figures) before calling the function.
When you use i
, it is assigned inside the second for
, so it will have a function as its value (highlighted in yellow in Figure 1).
Now, if you use j
, variable i
will keep its last value from the previous context: the for
over the list (Figure 2).