Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python local variable compile principle

def fun():  
    if False:
        x=3
    print(locals())
    print(x)
fun()

output and error message:

{}
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-57-d9deb3063ae1> in <module>()
      4     print(locals())
      5     print(x)
----> 6 fun()

<ipython-input-57-d9deb3063ae1> in fun()
      3         x=3
      4     print(locals())
----> 5     print(x)
      6 fun()

UnboundLocalError: local variable 'x' referenced before assignment

I am wondering how the python interpreter works. Note that x=3 doesn't run at all, and it shouldn't be treated as a local variable, which means the error would be " name 'x' is not defined". But look into the code and the error message, it isn't the case. Could anybody explain the mechanism principle of the compilation of the python interpreter behind this situation?

like image 677
Cheng CHEN Avatar asked Feb 25 '17 04:02

Cheng CHEN


People also ask

How does compile work in Python?

Python compile() function takes source code as input and returns a code object which is ready to be executed and which can later be executed by the exec() function. Parameters: Source – It can be a normal string, a byte string, or an AST object. Filename -This is the file from which the code was read.

What does compile () do?

The compile() function returns the specified source as a code object, ready to be executed.

How do I resolve UnboundLocalError local variables?

The Python "UnboundLocalError: Local variable referenced before assignment" occurs when we reference a local variable before assigning a value to it in a function. To solve the error, mark the variable as global in the function definition, e.g. global my_var .

How are local variables stored in Python?

Memory Allocation in Python The methods/method calls and the references are stored in stack memory and all the values objects are stored in a private heap.


1 Answers

So, Python will always categorize each name in each function as being one of local, non-local or global. These name scopes are exclusive; within each function (names in nested functions have their own naming scope), each name can belong to only one of these categories.

When Python compiles this code:

def fun():
    if False:
        x=3

it will result in an abstract syntax tree as in:

FunctionDef(
    name='fun', 
    args=arguments(...), b
    body=[
        If(test=NameConstant(value=False),
            body=[
                Assign(targets=[Name(id='x', ctx=Store())], value=Num(n=3))
            ], 
            orelse=[])
    ]
)

(some stuff omitted for brevity). Now, when this abstract syntax tree is compiled into code, Python will scan over all name nodes. If there is any Name nodes, with ctx=Store(), that name is considered to be local to the enclosing FunctionDef if any, unless overridden with global (i.e. global x) or nonlocal (nonlocal x) statement within the same function definition.

The ctx=Store() will occur mainly when the name in question is used on the left-hand side of an assignment, or as the iteration variable in a for loop.

Now, when Python compiles this to bytecode, the resulting bytecode is

>>> dis.dis(fun)
  4           0 LOAD_GLOBAL              0 (print)
              3 LOAD_FAST                0 (x)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

The optimizer removed the if statement completely; however since the variable was already marked local to the function, LOAD_FAST is used for x, which will result in x being accessed from local variables, and local variables only. Since x has not been set, the UnboundLocalError is thrown. The name print on the other hand was never assigned to, and thus is considered a global name within this function, so its value is loaded with LOAD_GLOBAL.