0

I have a question about local and global variables and global objects in Python. Look at this code:

var = 0

class Test():
    var = 0

test = Test()

def sum():
    Test.var += 1
    test.var += 1
    var += 1

sum()

If I run that code, the exception is triggered in the line "var += 1" only. The two previous lines work. I read this question from the Python FAQ. I think that there is no exception in the first two lines of the function because the "Test" and "test" are referenced. The member "var" is assigned, but "Test" and "test" are global because are referenced to get the member. The FAQ said: "In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a new value anywhere within the function’s body, it’s assumed to be a local."

So, the question is... is my assumption true?

Diego Woitasen
  • 32,892
  • 1
  • 18
  • 20

3 Answers3

2

Look at these functions:

def f():
    var += 1

def g():
    var = var.__iadd__(1)

g is the literal version of what the f function does (Of course Python uses the INPLACE_ADD opcode in the first version and do not lookup for the __iadd__ attribute).

But, as you can see, the name var is loaded once and stored once in both these functions. So, if you assign a variable, as the FAQ says, it is local unless you declare it global first.

Then, how can you load a local variable that do not exists yet to sum 1 and then store again with the same name?

The class attributes work because you are doing:

Test.var = Test.var.__iadd__(1)

and var do exist in the Test scope (so it can be looked up and reassigned).

JBernardo
  • 32,262
  • 10
  • 90
  • 115
1

I think that there is no exception in the first two lines of the function because the "Test" and "test" are referenced.

Correct. And they refer to the class attribute var, not the global one you defined.

The member "var" is assigned, but "Test" and "test" are global because are referenced to get the member.

Or to put it another way, Test and test are available in the global namespace so Test.var and test.var work.

If the value of var was not changed in sum(), you would get 0 since the lines above it have changed the Test class attribute not the global. Adding some prints in sum and removing the var += 1

def sum():
    Test.var += 1
    print Test.var
    test.var += 1
    print test.var
    print var

sum()

...gives:

1
2
0

But the moment I try to assign a value to var within the sum function, I get an error even before that line:

>>> def sum():
...     Test.var += 1
...     print Test.var
...     test.var += 1
...     print test.var
...     print var
...     var += 1
...     print var
... 
>>> sum()
1
2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in sum
UnboundLocalError: local variable 'var' referenced before assignment

Because var is now being assigned a value in sum(), it's considered local but has not been defined prior to that line. (Which implies that python is doing some 'looking ahead' or checking variable scope in sum() since it raised the error for the first print var before var was re-assinged. Putting var = 50 instead of var += 1 raises the same error.)

To work with the global var:

def sum():
    Test.var += 1
    print Test.var
    test.var += 1
    print test.var
    global var #added the global keyword
    print var
    var += 1
    print var

output:

1
2
0    # notice that global var is still 0 because the above var+=1 are for Test.var
1

Edit: Regarding the 'look ahead' behaviour I mentioned. Was going to post a question about it but it's been explained well in this answer: https://stackoverflow.com/a/370380/1431750 (to Python variable scope error)

Community
  • 1
  • 1
aneroid
  • 12,983
  • 3
  • 36
  • 66
0

In essence the rules are to avoid ambiguity:

var = 0 # a variable in the global namespace

class Test():
    var = 0 # this is a attribute of the Test class

test = Test()

def sum():
    Test.var += 1
    test.var += 1 # these explicity mention on which object the name 
                  # should be stored

    blah = var    # reads from a outer scope, but assigns to a local variable
    var = Test    # new local variable which shadows the outer name
                  # not great but understandable

    var += 1 # this looks like it assigns to a local variable
             # but since a name from a outer scope is read first,
             # Python is not sure what the programmer really want to do
             # (maybe assign to the outer scope, or first read it then shadow?)
             # instead of guessing Python raises an exception, 
             # forcing the programmer to use `global` or a different name
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • "Avoid ambiguity" is correct in spirit, but the last paragraph isn't quite accurate about how `var += 1` goes wrong. Python could never interpret `var += 1` as reading from a global variable then writing to a local. It could read from and write to the global, if `global var` was in play, otherwise it will both read from and write to a local `var`. If there isn't *already* a local `var` at that point, then you get an exception about trying to read from a non-existent variable. – Ben Aug 28 '12 at 04:25