The following code works as expected in both Python 2.5 and 3.0:
a, b, c = (1, 2, 3)
print(a, b, c)
def test():
print(a)
print(b)
print(c) # (A)
#c+=1 # (B)
test()
However, when I uncomment line (B), I get an UnboundLocalError: 'c' not assigned
at line (A). The values of a
and b
are printed correctly. This has me completely baffled for two reasons:
Why is there a runtime error thrown at line (A) because of a later statement on line (B)?
Why are variables
a
andb
printed as expected, whilec
raises an error?
The only explanation I can come up with is that a local variable c
is created by the assignment c+=1
, which takes precedent over the "global" variable c
even before the local variable is created. Of course, it doesn't make sense for a variable to "steal" scope before it exists.
Could someone please explain this behavior?
Here are two links that may help
1: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
2: docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference
link one describes the error UnboundLocalError. Link two can help with with re-writing your test function. Based on link two, the original problem could be rewritten as:
Python is a little weird in that it keeps everything in a dictionary for the various scopes. The original a,b,c are in the uppermost scope and so in that uppermost dictionary. The function has its own dictionary. When you reach the
print(a)
andprint(b)
statements, there's nothing by that name in the dictionary, so Python looks up the list and finds them in the global dictionary.Now we get to
c+=1
, which is, of course, equivalent toc=c+1
. When Python scans that line, it says "aha, there's a variable named c, I'll put it into my local scope dictionary." Then when it goes looking for a value for c for the c on the right hand side of the assignment, it finds its local variable named c, which has no value yet, and so throws the error.The statement
global c
mentioned above simply tells the parser that it uses thec
from the global scope and so doesn't need a new one.The reason it says there's an issue on the line it does is because it is effectively looking for the names before it tries to generate code, and so in some sense doesn't think it's really doing that line yet. I'd argue that is a usability bug, but it's generally a good practice to just learn not to take a compiler's messages too seriously.
If it's any comfort, I spent probably a day digging and experimenting with this same issue before I found something Guido had written about the dictionaries that Explained Everything.
Update, see comments:
It doesn't scan the code twice, but it does scan the code in two phases, lexing and parsing.
Consider how the parse of this line of code works. The lexer reads the source text and breaks it into lexemes, the "smallest components" of the grammar. So when it hits the line
it breaks it up into something like
The parser eventually wants to make this into a parse tree and execute it, but since it's an assignment, before it does, it looks for the name c in the local dictionary, doesn't see it, and inserts it in the dictionary, marking it as uninitialized. In a fully compiled language, it would just go into the symbol table and wait for the parse, but since it WON'T have the luxury of a second pass, the lexer does a little extra work to make life easier later on. Only, then it sees the OPERATOR, sees that the rules say "if you have an operator += the left hand side must have been initialized" and says "whoops!"
The point here is that it hasn't really started the parse of the line yet. This is all happening sort of preparatory to the actual parse, so the line counter hasn't advanced to the next line. Thus when it signals the error, it still thinks its on the previous line.
As I say, you could argue it's a usability bug, but its actually a fairly common thing. Some compilers are more honest about it and say "error on or around line XXX", but this one doesn't.
The Python interpreter will read a function as a complete unit. I think of it as reading it in two passes, once to gather its closure (the local variables), then again to turn it into byte-code.
As I'm sure you were already aware, any name used on the left of a '=' is implicitly a local variable. More than once I've been caught out by changing a variable access to a += and it's suddenly a different variable.
I also wanted to point out it's not really anything to do with global scope specifically. You get the same behaviour with nested functions.
The best way to reach class variable is directly accesing by class name