33

If I run the following code:

x = 1

class Incr:
    print(x)
    x = x + 1
    print(x)

print(x)

It prints:

1
2
1

Okay no problems, that's exactly what I expected. And if I do the following:

x = 1

class Incr:
    global x
    print(x)
    x = x + 1
    print(x)

print(x)

It prints:

1
2
2

Also what I expected. No problems there.

Now if I start making an increment function as follows:

x = 1

def incr():
    print(x)

incr()

It prints 1 just as I expected. I assume it does this because it cannot find x in its local scope, so it searches its enclosing scope and finds x there. So far no problems.

Now if I do:

x = 1

def incr():
    print(x)
    x = x + 1

incr()

This gives me the following error in the traceback:

UnboundLocalError: local variable 'x' referenced before assignment.

Why does Python not just search the enclosing space for x when it cannot find a value of x to use for the assignment like my class Incr did? Note that I am not asking how to make this function work. I know the function will work if I do the following:

x = 1

def incr():
    global x
    print(x)
    x = x + 1
    print(x)

incr()

This will correctly print:

1
2

just as I expect. All I am asking is why it doesn't just pull x from the enclosing scope when the keyword global is not present just like it did for my class above. Why does the interpreter feel the need to report this as an UnboundLocalError when clearly it knows that some x exists. Since the function was able to read the value at x for printing, I know that it has x as part of its enclosing scope...so why does this not work just like the class example?

Why is using the value of x for print so different from using its value for assignment? I just don't get it.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
Shashank
  • 13,713
  • 5
  • 37
  • 63
  • Is this Python2 or Python3? – Ethan Furman Sep 18 '13 at 04:54
  • Have you considered using `x+=1` in the `incr()` function? There might be a difference between `__add__` and `__iadd__` – inspectorG4dget Sep 18 '13 at 04:54
  • @Ethan It's Python 2.75. I used parentheses because I've been switching languages a lot lately and it's just more uniform. – Shashank Sep 18 '13 at 04:56
  • @inspectorG4dget If you try `x+=1` the same result occurs. – Shashank Sep 18 '13 at 04:57
  • It doesn't work with the function because of the first linked duplicate. It works with the class because the class does not create its own scope, so there is nowhere for locals to go and thus cause the problem. This is explained in the second linked duplicate. – Karl Knechtel Sep 10 '22 at 10:40

7 Answers7

46

Classes and functions are different, variables inside a class are actually assigned to the class's namespace as its attributes, while inside a function the variables are just normal variables that cannot be accessed outside of it.

The local variables inside a function are actually decided when the function gets parsed for the first time, and python will not search for them in global scope because it knows that you declared it as a local variable.

So, as soon as python sees a x = x + 1(assignment) and there's no global declared for that variable then python will not look for that variable in global or other scopes.

>>> x = 'outer'
>>> def func():
...     x = 'inner'  #x is a local variable now
...     print x
...     
>>> func()
inner

Common gotcha:

>>> x = 'outer'
>>> def func():
...     print x       #this won't access the global `x`
...     x = 'inner'   #`x` is a local variable
...     print x
...     
>>> func()
...
UnboundLocalError: local variable 'x' referenced before assignment

But when you use a global statement then python for look for that variable in global scope.

Read: Why am I getting an UnboundLocalError when the variable has a value?

nonlocal: For nested functions you can use the nonlocal statement in py3.x to modify a variable declared in an enclosing function.


But classes work differently, a variable x declared inside a class A actually becomes A.x:

>>> x = 'outer'
>>> class A:
...    x += 'inside'  #use the value of global `x` to create a new attribute `A.x`
...    print x        #prints `A.x`
...     
outerinside
>>> print x
outer

You can also access the class attributes directly from global scope as well:

>>> A.x
'outerinside'

Using global in class:

>>> x = 'outer'
>>> class A:
...     global x
...     x += 'inner' #now x is not a class attribute, you just modified the global x
...     print x
...     
outerinner
>>> x
'outerinner'
>>> A.x
AttributeError: class A has no attribute 'x'

Function's gotcha will not raise an error in classes:

>>> x = 'outer'
>>> class A:
...     print x                      #fetch from globals or builitns
...     x = 'I am a class attribute' #declare a class attribute
...     print x                      #print class attribute, i.e `A.x`
...     
outer
I am a class attribute
>>> x
'outer'
>>> A.x
'I am a class attribute'

LEGB rule: if no global and nonlocal is used then python searches in this order.

>>> outer = 'global'
>>> def func():
        enclosing = 'enclosing'
        def inner():
                inner = 'inner'
                print inner           #fetch from (L)ocal scope
                print enclosing       #fetch from (E)nclosing scope
                print outer           #fetch from (G)lobal scope
                print any             #fetch from (B)uilt-ins
        inner()
...         
>>> func()
inner
enclosing
global
<built-in function any>
Alex
  • 3,958
  • 4
  • 17
  • 24
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • Why does your Common Gotcha throw an error but OP's incr() function that solely calls print(x) print the global value of 1. – Max Apr 04 '16 at 15:21
  • The article you cited has a great explanation. In your example, because there is an assignment to x, the variable is declared as local. While in OP's example, x is defined as a global variable to the function incr(). – Max Apr 04 '16 at 15:30
5

From Python scopes and namespaces:

It is important to realize that scopes are determined textually: the global scope of a function defined in a module is that module’s namespace, no matter from where or by what alias the function is called. On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)

Which means that, the scope for x = x + 1 is determined statically, before the function is called. And since this is an assignment, then 'x' becomes a local variable, and not looked up globally.

That is also the reason why from mod import * is disallowed in functions. Because the interpreter won't import modules for you in compile time to know the names you are using in the function. i.e, it must know all names referenced in the function at compile time.

MadeOfAir
  • 2,933
  • 5
  • 31
  • 39
4

It's the rule Python follows - get used to it ;-) There is a practical reason: both the compiler and human readers can determine which variables are local by looking only at the function. Which names are local has nothing to do with the context in which a function appears, and it's generally a Very Good Idea to follow rules that limit the amount of source code you have to stare at to answer a question.

About:

I assume it does this because it cannot find x in its local scope, so it searches its enclosing scope and finds x.

Not quite: the compiler determines at compile time which names are and aren't local. There's no dynamic "hmm - is this local or global?" search going on at runtime. The precise rules are spelled out here.

As to why you don't need to declare a name global just to reference its value, I like Fredrik Lundh's old answer here. In practice, it is indeed valuable that a global statement alerts code readers to that a function may be rebinding a global name.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
1

Because that's the way it was designed to work.

Basically, if you have an assignment anywhere in your function, then that variable becomes local to that function (unless you've used global, of course).

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 3
    Okay I understand this, but why can't it just work like the class example? In the class example Incr was able to use the value of x from an enclosing scope to assign to some local x and increment that value by 1. So why can't functions do the same? – Shashank Sep 18 '13 at 04:54
  • 1
    They **could**. But they don't and never will ;-) I expect Guido would consider the class example to be a bug, but I haven't seen him comment on it. – Tim Peters Sep 18 '13 at 05:06
1

Because it would lead to very hard to track down bugs!

When you type that x = x + 1, you could have meant to increment an x in the enclosing scope... or you could have simply forgotten that you already used x somewhere else and been trying to declare a local variable.

I would prefer the interpreter to only allow you to change the parent namespace if you intend to- this way you can't do it by accident.

Paul Becotte
  • 9,767
  • 3
  • 34
  • 42
0

I can try and make an educated guess why it works this way.

When Python encounters a string x = x + 1 in your function, it has to decide where to look up the x.

It could say "the first occurrence of x is global, and the second one is local", but this is quite ambiguous (and therefore against Python philosophy). This could be made part of the syntax, but it potentially leads to tricky bugs. Therefore it was decided to be consistent about it, and treat all occurrences as either global or local variables.

There is an assignment, therefore if x was supposed to be global, there would be a global statement, but none is found.

Therefore, x is local, but it is not bound to anything, and yet it is used in the expression x + 1. Throw UnboundLocalError.

fjarri
  • 9,546
  • 39
  • 49
  • I just tested and the `x = x + 1` example works in a class definition, though. – Nicole Sep 18 '13 at 04:58
  • In his example, I think there are *three* ways that occurrences are treated: globals, locals, and closed over values. – Neil G Sep 18 '13 at 04:58
  • 1
    If it's ambiguous and therefore against Python philosophy, then why is it allowed in classes? In the absence of a `global` keyword it is my opinion that it should simply assign global(x) + 1 to a local(x) just like it did in the class definition. I guess all I'm asking is, why does this work differently for classes than in functions – Shashank Sep 18 '13 at 05:02
  • Yep, I didn't notice your class example (it's actually the first time I see any code execution in a class definition in Python, so my mind just ignored it :) @Ashwini's answer provides more details on this. – fjarri Sep 18 '13 at 05:07
-1

As an extra example to the newly created A.x created within the class. The reassigning of x to 'inner' within the class does not update the global value of x because it is now a class variable.

x = 'outer'
class A:
    x = x
    print(x)
    x = 'inner'
    print(x)

print(x)
print(A.x)
Max
  • 2,072
  • 5
  • 26
  • 42