Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closure in python?

When I run this code, I get this result:

15
15

I expect the output should be

15
17

but it is not. The question is: why?

def make_adder_and_setter(x):
    def setter(n):
        x = n

    return (lambda y: x + y, setter)

myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)
like image 294
Racket Noob Avatar asked Nov 28 '22 15:11

Racket Noob


1 Answers

You are setting a local variable x in the setter() function. Assignment to a name in a function marks it as a local, unless you specifically tell the Python compiler otherwise.

In Python 3, you can explicitly mark x as non-local using the nonlocal keyword:

def make_adder_and_setter(x):
    def setter(n):
        nonlocal x
        x = n

    return (lambda y: x + y, setter)

Now x is marked as a free variable and looked up in the surrounding scope instead when assigned to.

In Python 2 you cannot mark a Python local as such. The only other option you have is marking x as a global. You'll have to resort to tricks where you alter values contained by a mutable object that lives in the surrounding scope.

An attribute on the setter function would work, for example; setter is local to the make_adder_and_setter() scope, attributes on that object would be visible to anything that has access to setter:

def make_adder_and_setter(x):
    def setter(n):
        setter.x = n
    setter.x = x

    return (lambda y: setter.x + y, setter)

Another trick is to use a mutable container, such as a list:

def make_adder_and_setter(x):
    x = [x]
    def setter(n):
        x[0] = n

    return (lambda y: x[0] + y, setter)

In both cases you are not assigning to a local name anymore; the first example uses attribute assignment on the setter object, the second alters the x list, not assign to x itself.

like image 180
Martijn Pieters Avatar answered Dec 05 '22 13:12

Martijn Pieters