7

Why do I get error in 3rd code below, but not in first two? I am using Python 3.6.0, Anaconda 4.3.1 (64-bit) Jupyter.

Code 1:

c = 100
def fib():
    c = 20
    a = c
    print ("a in fib", a)
    print ("c in fib", c)
fib()
print ("c", c)

Output of Code 1:

a in fib 20
c in fib 20
c 100

Code 2:

c = 100
def fib():
    a = c
    print ("a in fib", a)
    print ("c in fib", c)   
fib()
print ("c", c)

Output of Code 2:

a in fib 100
c in fib 100
c 100

Code 3:

c = 100
def fib():
    a = c
    print ("a in fib", a)
    print ("c in fib", c)
    c = 20  
fib()
print ("c", c)

Output of Code 3:


UnboundLocalError                         Traceback (most recent call last)
<ipython-input-42-d6affa26dc65> in <module>()
      7 
      8 
----> 9 fib()
     10 print ("c", c)

<ipython-input-42-d6affa26dc65> in fib()
      1 c = 100
      2 def fib():
----> 3     a = c
      4     print ("a in fib", a)
      5     print ("c in fib", c)

UnboundLocalError: local variable 'c' referenced before assignment
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
  • 1
    I changed your title. I'm not entirely happy with the wording, but I think it is more descriptive than the original. – timgeb Apr 05 '17 at 12:55

7 Answers7

4

This behavior is called lexical scoping. Here's the relevant part of the official Python docs (emphasis mine):

If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

And for the definition of "name binding operations" (emphasis mine again):

Names refer to objects. Names are introduced by name binding operations.

The following constructs bind names: formal parameters to functions, import statements, class and function definitions (these bind the class or function name in the defining block), and targets that are identifiers if occurring in an assignment, for loop header, or after as in a with statement or except clause. The import statement of the form from ... import * binds all names defined in the imported module, except those beginning with an underscore. This form may only be used at the module level.

A target occurring in a del statement is also considered bound for this purpose (though the actual semantics are to unbind the name).

So in your 3rd code example, the c = 20 at the end of the block qualifies as a name binding operation via the "targets that are identifiers if occurring in an assignment" clause. That makes all occurrences of c in that function refer to the local variable c. At runtime, though, your code hits the a = c line first. Since c hasn't actually been defined in the local scope yet, you get the exception.

In the first block, you define c locally before you try to reference it, so no problem there.

In the second, you don't perform any name binding operations on c within the function, so Python assumes you want the outer definition.

As to why it works this way, see this question:

The rationale for lexical scoping is not just for performance (full lexical scoping including closures actually has a performance cost, see the funcarg problems), it's for simplicity and reliability. While it may be surprising when first learning the language, the rules are actually dead simple so an experienced programmer can instantly tell which use of an identifier refers to which scope. One can understand functions in isolation, as execution isn't affected by who calls this functions and how they decided to name their variables.

Community
  • 1
  • 1
glibdud
  • 7,550
  • 4
  • 27
  • 37
  • To be honest, I think that behavior is a wart and surprising. Does anyone know the reasoning for this? What's the problem with changing `s` from global to local on the fly? – timgeb Apr 05 '17 at 12:58
  • 1
    @timgeb As it turns out, that's been asked here before. It's not a terribly deep explanation, but see update. – glibdud Apr 05 '17 at 13:24
  • Alright, thanks for providing the rationale. I may not agree. :) – timgeb Apr 05 '17 at 13:26
1

I can't say for certain, but I think the most likely explanation is that the first two see a global version of the variable 'c'.

The third one must see that you define a local variable 'c' at the bottom of the 'fib' function and so doesn't use the global 'c' variable. Really, it's a pretty good example of why you shouldn't use global variables in this way.

1

This is the official explanation you're looking for

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment

This is because when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable. Consequently when the earlier print(x) attempts to print the uninitialized local variable and an error results.

shizhz
  • 11,715
  • 3
  • 39
  • 49
1

Well that is an interesting corner case. The reason comes from the way Python binds variable to a block:

4.2.1. Binding of names
...
The following constructs bind names: ... targets that are identifiers if occurring in an assignment.... If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal or global.

In first block, c is local to function and is assigned before use: fine and the global C in unchanged by the fib() function call

In second block c is not assigned in function definition: a=c uses the global variable

In third, c is assigned inside the bloc, so it is local. But it is used before assignement, hence the error. If you want to use the global value, you must declare it as global:

def fib():
    global c
    a = c
    print ("a in fib", a)
    print ("c in fib", c)
    c = 20
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

Python "assumes" that we want a local variable due to the assignment to a inside of fib(), so the first print statement throws this error message. Any variable which is changed or created inside of a function is local, if it hasn't been declared as a global variable. To tell Python, that we want to use the global variable, we have to use the keyword "global", as can be seen in the following example:

c = 100
def fib():
    global c
    a = c
    print ("a in fib", a)
    print ("c in fib", c)
    c = 20  
fib()
print ("c", c)
phunsukwangdu
  • 412
  • 5
  • 13
0

When we use global variables we fundamentally make it available to all the functions that we have defined or plan to define. In your code when you say c=100 You have a global variable defined. But to use it inside a function without being hidden by a local variable, you have do this

def fib():
   global c
   print(c)
   c=10
   a=c
   print(a,c)

fib()

Output: 100 10 10

Local variables need definition to be referenced in the same scope.

-1

This is because you can use but not change a string value in Python (once assigned).

You can "update" an existing string by (re)assigning a variable to another string. [ref]

In your scenario, you're using variable c and assigning it to a, but further you must play with your newly created variable a and not change c.

Community
  • 1
  • 1
vsr
  • 1,025
  • 9
  • 17