lundiAuSoleil
lundiAuSoleil

Reputation: 55

python and UnboundLocalError

I have a little problem with local variables and python (2.7).

I have a little code :

def foo(a):
    def bar():
        print a
    return bar()

>>>foo(5)
5

Well, it's working, but if want to modify a , like this :

def foo(a):
    def bar():
        a -= 1
    return bar()
>>>foo(5) 
UnboundLocalError: local variable 'a' referenced before assignment

So I must affect 'a' to another variable.

But I don't understand this comportment. Is it because when there is an assignment, python looks in the locals() variables and doesn't find it ?

Thanks.

Upvotes: 2

Views: 259

Answers (2)

Katriel
Katriel

Reputation: 123782

You've found something that used to be an issue in Python! The short answer is that you can't do this in Python 2.x (though you can simulate) it, but you can in 3.x using the nonlocal keyword.

See PEP 3104:

Before version 2.1, Python's treatment of scopes resembled that of standard C: within a file there were only two levels of scope, global and local. In C, this is a natural consequence of the fact that function definitions cannot be nested. But in Python, though functions are usually defined at the top level, a function definition can be executed anywhere. This gave Python the syntactic appearance of nested scoping without the semantics, and yielded inconsistencies that were surprising to some programmers -- for example, a recursive function that worked at the top level would cease to work when moved inside another function, because the recursive function's own name would no longer be visible in its body's scope. This violates the intuition that a function should behave consistently when placed in different contexts. Here's an example:

def enclosing_function():
    def factorial(n):
        if n < 2:
            return 1
        return n * factorial(n - 1)  # fails with NameError
    print factorial(5)

Python 2.1 moved closer to static nested scoping by making visible the names bound in all enclosing scopes (see PEP 227). This change makes the above code example work as expected. However, because any assignment to a name implicitly declares that name to be local, it is impossible to rebind a name in an outer scope (except when a global declaration forces the name to be global). Thus, the following code, intended to display a number that can be incremented and decremented by clicking buttons, doesn't work as someone familiar with lexical scoping might expect:

def make_scoreboard(frame, score=0):
    label = Label(frame)
    label.pack()
    for i in [-10, -1, 1, 10]:
        def increment(step=i):
            score = score + step  # fails with UnboundLocalError
            label['text'] = score
        button = Button(frame, text='%+d' % i, command=increment)
        button.pack()
    return label

Python syntax doesn't provide a way to indicate that the name score mentioned in increment refers to the variable score bound in make_scoreboard, not a local variable in increment. Users and developers of Python have expressed an interest in removing this limitation so that Python can have the full flexibility of the Algol-style scoping model that is now standard in many programming languages, including JavaScript, Perl, Ruby, Scheme, Smalltalk, C with GNU extensions, and C# 2.0.

Upvotes: 8

Niek de Klein
Niek de Klein

Reputation: 8834

The reason given by Constantinius is correct. Another way of dealing with it (without using global variables) would be

def foo(a):
    def bar(a):
        a -= 1
        return a
    return bar(a)
>>> print foo(5) 
4

Upvotes: 2

Related Questions