Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the following code always output 16?

Tags:

python

def makeActions():
    acts=[]
    for i in range(5):
        print len(acts)
        acts.append(lambda x: i ** x)
        print acts[i]
    return acts
acts=makeActions()
for i in range(5):
    print acts[i](2)

Output:

16
16
16
16
16

Expected output:

0
1
4
9
16
like image 585
user198729 Avatar asked Nov 27 '22 01:11

user198729


2 Answers

Because the i in the lambda is probably not what you expect. To verify this, change the code:

acts.append(lambda x: (i, i ** x))

Now the print tells you the value of i:

(4, 16)
(4, 16)
(4, 16)
(4, 16)
(4, 16)

This means that the lambda doesn't copy the value of i but keeps a reference to the variable, so all lambdas see the same value. To fix this, copy i:

acts.append(lambda x, i=i: (i, i ** x))

The little i=i creates a local copy of i inside the lambda.

[EDIT] Now why is this? In the versions of Python before 2.1, local functions (i.e. functions defined inside of other functions) couldn't see the variables in the same scope.

def makeActions():
    acts=[]
    for i in range(5):
        print len(acts)
        def f(x):   # <-- Define local function
            return i ** x
        acts.append(f)
        print acts[i]
    return acts

then you'd get an error that i isn't defined. lambda could see the enclosing scope at the cost of a somewhat wierd syntax.

This behavior has been fixed in one of the recent versions of Python (2.5, IIRC). With these old versions of Python, you'd have to write:

        def f(x, i=i):   # <-- Must copy i
            return i ** x

Since the fix (see PEP 3104), f() can see variables in the same scope, so lambda isn't necessary anymore.

like image 165
Aaron Digulla Avatar answered Dec 11 '22 12:12

Aaron Digulla


Because all lambda functions you create are bound to i, which becomes 4 at the end of loop, and as we all well know 4*4 = 16

to avoid that create your functions using nested function(closure) e.g.

def makePowerFunc(base):
    def powerFunc(x):
        return base**x
    return powerFunc

def makeActions():
    acts=[]
    for i in range(5):
        acts.append(makePowerFunc(i))

    return acts
acts=makeActions()
for i in range(5):
print acts[i](2)

output:

0
1
4
9
16

There are other ways to solve it, but it is better to have a named nested function instead of lambda, and you can do many more things with such closures

like image 32
Anurag Uniyal Avatar answered Dec 11 '22 12:12

Anurag Uniyal