function object and copies of variables

Steven Bethard steven.bethard at gmail.com
Tue Nov 2 19:55:34 CET 2004


 <stan <at> saticed.me.uk> writes:
> 
> can a function object store values so this 3 liner can print 0 1 instead
> of 1 1?
> 
> f = []
> for i in [0, 1]: f.append(lambda: i)
> for j in f: print j()

Just thought I might give a little extra commentary on why Diez B. Roggisch's
solution works.  First, I'm going to write it in a more readable (IMHO) form[1]:

>>> f = []
>>> for i in (0, 1):
... 	def func(i=i):
... 		return i
... 	f.append(func)
... 	
>>> for j in f:
... 	print j()
... 	
0
1

Now, the problem you're running into here is that def (and lambda) in Python
don't bind names in their body to values at the time at which the function is
defined.  If they did, the following would be invalid:

>>> def f():
... 	def g():
... 		print j
... 	j = 1
... 	g()
... 	
>>> f()
1

because the name 'j' is not bound to a value until after g is defined.  Instead,
what happens is that the g() function doesn't try to look up a value for j until
it reaches the statement 'print j'.  Since g() is called after the name 'j' is
bound to the value 1, when g() does finally look up a value for j, it is able to
find one in the f function's scope.

Back to your original question, what happens when you write:

>>> f = []
>>> for i in (0, 1):
... 	def func():
... 		return i
... 	f.append(func)
... 
>>> for j in f:
... 	print j()
... 
1
1

(or the equivalent) is that the only i available to func is the one from the for
loop, and this one has the value 1:

>>> for i in (0, 1):
... 	pass
... 
>>> i
1

So, when func() is called by calling j(), func looks for i, finds it in the
global scope, and uses that value, which is 1 both times that func() is called,
because it is called after the end of the first for loop.  Contrast this with:

>>> for i in (0, 1):
... 	def func():
... 		return i
... 	print func()
... 	
0
1

where the for-loop has not completed, and so i still has the value 0 the first
time func() is called.

Diez B. Roggisch's solution solves your problem by binding i to a value at the
time that func is defined, taking advantage of the fact that default argument
values are evaluated and bound at function definition time.

Hope this is helpful.

Steve

[1] If you're really looking for the fewer lines, try:

for j in [lambda i=i: i for i in (0, 1)]:
    print j()

Not only is it more readable (IMHO), it's also probably faster since list
comprehensions are almost always faster than a for-loop with an append.




More information about the Python-list mailing list