views:

144

answers:

4

I started working in Python just recently and haven't fully learned all the nuts and bolts of it, but recently I came across this post that explains why python has closures, in there, there is a sample code that goes like this:

y = 0

def foo():
    x = [0]
    def bar():
        print x[0], y
    def change(z):
        global y
        x[0] = y = z

    change(1)
    bar()
    change(2)
    bar()
    change(3)
    bar()
    change(4)
    bar()

foo()

1 1
2 2
3 3

and basically I don't understand how it actually works, and what construct like x[0] does in this case, or actually I understand what it's doing, I just don't get how is it this :)

+4  A: 

It might be simpler to understand if you look at this simplified code where I have removed the global variable:

def foo():
    x = [0]
    def bar():
        print x[0]
    def change(z):
        x[0] =  z

    change(1)
    bar()

foo()

The first line in foo creates a list with one element. Then bar is defined to be a function which prints the first element in x and the function change modifies the first element of the list. When change(1) is called the value of x becomes [1].

Mark Byers
ok, makes sense. but why then if we would've defined x as an integer, say x = 0, then it's value wouldn't change inside the change() function? like they have it in the second block of code in that post?
Alex N.
@Alex N.: That's actually explained in the article, and also Alex Martelli has also covered it in his post too.
Mark Byers
+1  A: 

x = [0] creates a new list with the value 0 in it. x[0] references the zero-eth element in the list, which also happens to be zero.

The example is referencing closures, or passable blocks of code within code.

mvid
+6  A: 

Before the nonlocal keyword was added in Python 3 (and still today, if you're stuck on 2.* for whatever reason), a nested function just couldn't rebind a local barename of its outer function -- because, normally, an assignment statement to a barename, such as x = 23, means that x is a local name for the function containing that statement. global exists (and has existed for a long time) to allow assignments to bind or rebind module-level barenames -- but nothing (except for nonlocal in Python 3, as I said) to allow assignments to bind or rebind names in the outer function.

The solution is of course very simple: since you cannot bind or rebind such a barename, use instead a name that is not bare -- an indexing or an attribute of some object named in the outer function. Of course, said object must be of a type that lets you rebind an indexing (e.g., a list), or one that lets you bind or rebind an attribute (e.g., a function), and a list is normally the simplest and most direct approach for this. x is exactly that list in this code sample -- it exists only in order to let nested function change rebind x[0].

Alex Martelli
+2  A: 

This code is trying to explain when python creates a new variable, and when python reuses an existing variable. I rewrote the above code slightly to make the point more clear.

y = "lion"
def foo():
    x = ["tiger"]
    w = "bear"
    def bar():
        print y, x[0], w
    def change(z):
        global y
        x[0] = z
        y = z
        w = z
    bar()
    change("zap")
    bar()
foo()

This will produce this output:

lion tiger bear
zap zap bear

The point is that the inner function change is able to affect the variable y, and the elements of array x, but it does not change w (because it gets its own local variable w that is not shared).

Matt S