Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnboundLocalError: local variable referenced before assignment in python closure

I implemented two simple closures in Python. To me, they looks the same, but one works and the other doesn't.

The working one is:

def makeInc(x, y):
    def inc():
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)

inc5 () # returns 15
inc10() # returns 15

But the second one doesn't work:

import os
def linker(dest, filename):
    print filename
    def link(): 
        if os.path.isfile(filename): # line 17
            filename = os.path.join(os.getcwd(), filename)
            dest = os.path.join(dest, filename)
            y = rawinput('[y]/n: ln -sf %s %s' % (dest, filename))
            if y == 'n':
                return 1
            else:
                return os.system('ln -sf %s %s' %(dest, filename))
        else:
            return -1
    return link

l = linker('~', '.vimrc')
l()  # line 30

It faults at the first line of link() when executing l():

Traceback (most recent call last):
  File "test.py", line 30, in <module>
    l()
  File "test.py", line 17, in link
    if os.path.isfile(filename):
UnboundLocalError: local variable 'filename' referenced before assignment

They seem identical to me so I don't understand why the second one doesn't work. Any idea?

like image 985
qweruiop Avatar asked Apr 15 '15 00:04

qweruiop


1 Answers

You have overwritten the variable with filename = os.path.join(os.getcwd(), filename), if you change the filename = to something other than filename you won't get a local variable 'filename' referenced before assignment error.

Once you set filename = you are no longer referring to the parameter filename that is passed in you are referring to the local filename in the scope of the inner function which you try to use in the if before you have it defined.

You will have the same problem with dest, if you change the two lines and the other variables to something like:

filename_ = os.path.join(os.getcwd(), filename)
dest_ = os.path.join(dest, filename)

You will see the code runs fine as filename now refers to the parameter not to a local variable defined in your inner function.

You will see the exact same behaviour if you try to reassign x in your first function and try to access x before you have defined it:

def makeInc(x, y):
    def inc():
        print  y + x # will cause referenced before assignment error
        x = 5 # now x is local to the inner func, the x from the outer function is overridden
        return y + x
    return inc

If you print the __closure__ attribute you will see what happens:

def makeInc(x, y):
    def inc():
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)
print(inc5.__closure__)
(<cell at 0x7f180df67e50: int object at 0xef00f8>, <cell at 0x7f180df67fa0: int object at 0xef0080>)

Now reassigning x:

def makeInc(x, y):
    def inc():
        print  y + x
        x= 5
        return y + x
    return inc

inc5 = makeInc(5, 10)
inc10 = makeInc(10, 5)
print(inc5.__closure__)
(<cell at 0x7fea11889fd8: int object at 0x291e080>,)

After reassigning in the inner function, there is no longer a reference to x.

So basically the fundamental difference between your two original functions is that in one you are reassigning the variable in the local scope and in the other you are not. As you can see from the code above if you do something similar in the first function the outcome is exactly the same.

There is a nice tut here on scopes LEGB etc..

like image 112
Padraic Cunningham Avatar answered Oct 13 '22 08:10

Padraic Cunningham