views:

109

answers:

3

why does this work:

def function1():                                                                                                             
       a = 10                                                                                                                    
       def function2():
          print a
       function2()

but this does not:

def function1():
    a = 10
    def function2():
        print a
        a -= 1
        if a>0:
           function2()
    function2()

error:

UnboundLocalError: local variable 'a' referenced before assignment
+2  A: 

In the non-working snippet, you assign to a when you say "a -= 1". Because of that, inside function2, a is local to that scope, not taken the enclosing scope. Python's closures are lexical—it does not dynamically look for a in function2's frame and if it has not been assigned go and look for it in function1's frame.

Note that this doesn't depend on the recursiveness or using closures at all. Consider the example of this function

def foo():
    print a
    a = 4

Calling it will get you an UnboundLocalError too. (Without a = 4 it would either use the global a or, if there isn't one, raise a NameError.) Because a is potentially assigned within that scope, it is local.


If I was designing this function, I might use an approach more like

def function1():
    a = 10
    def function2(a=a):
        print a
        a -= 1
        if a > 0:
           function2(a)
    function2()

(or of course for a in xrange(10, -1, -1): print a ;-) )

Mike Graham
+4  A: 

The error doesn't seem to be very descriptive of the root problem. Mike explains the messages but that does not explain the root cause.

The actual problem is that in python you cannot assign to closed over variables. So in function2 'a' is read only. When you assign to it you create a new variable which, as Mike points out, you read before you write.

If you want to assign to the to the outer variable from the inner scope you have to cheat like so:

def function1():
    al = [10]
    def function2():
        print al[0]
        al[0] -= 1
        if al[0]>0:
           function2()
    function2()

So al is immutable but its contents are not and you can change them without creating a new variable.

charlieb
Indeed, this is the key point in designing this function—you cannot assign to non-local scopes. (Note: `al` is *mutable*; that's why this works.)
Mike Graham
I think it is important, for the sake of clarity, to distinguish between al the variable and the values that al contains. It always comes back to pointers for me so let me say this; you cannot make al point to a new list but you can change the contents of the list that al points to. al->[v1, v2, v3] al cannot be changed but v1, v2 and v3 can be changed. Mike is absolutely correct that this makes al mutable because in our terminology al *is* the list not the pointer to the list.
charlieb
+1 very nice answer.
Goose Bumper
+1  A: 

It should be noted that this is a syntax glitch in Python. Python itself (at the bytecode level) can assign to these variables just fine; there's simply no syntax in 2.x to indicate that you want to do so. It assumes that if you assign to a variable in a nesting level, you mean for it to be a local to it.

This is a huge shortcoming; being able to assign to closures is fundamental. I've worked around this with charlieb's hack several times.

Python 3 fixes this with the very awkwardly-named "nonlocal" keyword:

def function1():
    a = 10
    def function2():
        nonlocal a
        print(a)
        a -= 1
        if a>0:
           function2()
    function2()
function1()

It's very poor that this syntax is only available in 3.x; most people are stuck in 2.x, and have to continue using hacks to work around this problem. This badly needs to be backported to 2.x.

http://www.python.org/dev/peps/pep-3104/

Glenn Maynard