2

I am trying to understand how variables are managed internally by Python.

x = 10
def ex1():
    if False:
        x=1
    print(x)
ex1()

When ex1() is executed, it shows an UnboundLocalError since local variable 'x' is not referenced.

How does this happen? Does the parsing happen in an initial pass and just create the symbol table and specifies scope followed by the interpretation which happens in another pass and skips x=1 as it is unreachable?

2 Answers2

0

Python doesn't have variable declarations, so when variables are created/used it has to determine the scope itself. Python scoping is lexical, meaning that it can access a variable in its enclosed scope, it cannot modify them. The way ex1() is written, x=1 is local to ex1(). However, when you go to run ex1() it tries to read x=10 as it is local, and throws your UnboundLocalError. So, the way that the variable is managed, is it sees a local declaration, runs the function, and sees another local declaration, and due to scope, cannot relate the two.

nldoty
  • 471
  • 3
  • 8
0

Conceptually this make sense. I can't tell how its implemented but I can tell why.

When you affect a variable it gets affected in the local scope unless you explicitly tell by using the keyword global. If you only access it and there is no affectation it will implicitly use the global variable since no local variable is defined.

x = 10

def access_global():
    print x

def affect_local():
    x = 0
    print x

def affect_global():
    global x
    x = 1
    print x

access_global() # 10
affect_local()  # 0
print x         # 10
affect_global() # 1
print x         # 10

If you do this inside a nested function, class or module the rules are similar:

def main():
    y = 10
    def access():
        print y
    def affect():
        y = 0
        print y

    access() # 10
    affect() # 0
    print y  # 10

main()

This is probably saving hours of painful debugging by never overwriting variable of parent scopes unless its explicitly stated.

EDIT

disassembling the python byte code gives us some additional information to understand:

import dis
x = 10
def local():
    if False:
        x = 1

def global_():
    global x
    x = 1

print local
dis.dis(local)
print global_
dis.dis(global_)


<function local at 0x7fa01ec6cde8>
 37           0 LOAD_GLOBAL              0 (False)
              3 POP_JUMP_IF_FALSE       15

 38           6 LOAD_CONST               1 (1)
              9 STORE_FAST               0 (x)
             12 JUMP_FORWARD             0 (to 15)
        >>   15 LOAD_CONST               0 (None)
             18 RETURN_VALUE        
<function global_ at 0x7fa01ec6ce60>
 42           0 LOAD_CONST               1 (1)
              3 STORE_GLOBAL             0 (x)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE      

We can see that the byte code for the local function is calling STORE_FAST and the global_ function calls STORE_GLOBAL.

This question also explain why its more performant to translate function to byte code to avoid compiling every time the function is called: Why python compile the source to bytecode before interpreting?

Community
  • 1
  • 1
Lynch
  • 9,174
  • 2
  • 23
  • 34