Python local vs global variables

2019-01-15 08:06发布

问题:

I understand the concept of local and global variables in Python, but I just have a question about why the error comes out the way it is in the following code. Python execute the codes line by line, so it does not know that a is a local variable until it reads line 5. Does Python go back one line and tag it as an error after it tries to execute line 5?

a=0

def test():
    print a  #line 4, Error : local variable 'a' referenced before assignment
    a=0      #line 5

test()

回答1:

Setup and Testing

To analyze your question, let's create two separate test functions that replicate your issue:

a=0

def test1():
    print(a)

test1()

prints 0. So, calling this function is not problematic, but on the next function:

def test2():
    print(a)  # Error : local variable 'a' referenced before assignment
    a=0  

test2()

We get an error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in test2
UnboundLocalError: local variable 'a' referenced before assignment

Disassembly

We can disassemble the two functions (first Python 2):

>>> import dis
>>> dis.dis(test1)
  2           0 LOAD_GLOBAL              0 (a)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        

And we see that the first function automatically loads the global a, while the second function:

>>> dis.dis(test2)
  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  3           5 LOAD_CONST               1 (0)
              8 STORE_FAST               0 (a)
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE      

seeing that a is assigned inside it, attempts to LOAD_FAST from the locals (as a matter of optimization, as functions are pre-compiled into byte-code before running.)

If we run this in Python 3, we see nearly the same effect:

>>> test2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in test2
UnboundLocalError: local variable 'a' referenced before assignment
>>> 
>>> import dis
>>> dis.dis(test1)
  2           0 LOAD_GLOBAL              0 (print) 
              3 LOAD_GLOBAL              1 (a) 
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
              9 POP_TOP              
             10 LOAD_CONST               0 (None) 
             13 RETURN_VALUE     

>>> dis.dis() # disassembles the last stack trace
  2           0 LOAD_GLOBAL              0 (print) 
    -->       3 LOAD_FAST                0 (a) 
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
              9 POP_TOP              

  3          10 LOAD_CONST               1 (0) 
             13 STORE_FAST               0 (a) 
             16 LOAD_CONST               0 (None) 
             19 RETURN_VALUE        

We see our error is on the LOAD_FAST again.



回答2:

Python doesn't execute line by line in the function code you submitted. It has first to parse it as a bloc of execution. It decides if a variable is local or global depending if it is written at (function) local level. As it is the case, it decides that the variable is local, hence the error.



回答3:

This is because, in python, you need to tell that you are going to modify the value of a global variable. You do that as:

def test():
  global a
  print a  #line 4, Error : local variable 'a' referenced before assignment
  a=0      #line 5

With global a, you can modify variables in the function. You may want to have a look at this:

  • Python: Why is global needed only on assignment and not on reads?


回答4:

Short explanation:

a variable is bound to the test function. When you attempt to print it, Python throws the error because the local variable a will be initialized later. But if you remove the a=0, the code will execute without any problems and output 0.

Longer explanation

Python interpreter starts searching for the variable named a from the local scope. It sees that a is indeed declared within the function and initialized only on the fifth line. Once it's found, the lookup is over.

When it tries to process print a (forth line) it says 'Oh boy, I need to print a variable that's not initialized yet, I'd better throw an error.'

Explanation based on function's bytecode

If you run this code:

import dis

a = 0

def test():
  print a  #line 4, Error : local variable 'a' referenced before assignment
  a = 0    #line 5

dis.dis(test)

you'll get this output:

  6           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  7           5 LOAD_CONST               1 (0)
              8 STORE_FAST               0 (a)
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

Explanation on the unclear parts of the above:

  • LOAD_FAST means that a's reference is being pushed onto the stack.
  • STORE_FAST means that Python assigns 0 (from LOAD_CONST) to the local variable a

So, the problem is that LOAD_FAST goes before STORE_FAST.