Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable scope in nested functions

Could someone explain why the following program fails:

def g(f):
  for _ in range(10):
    f()

def main():
  x = 10
  def f():
    print x
    x = x + 1
  g(f)

if __name__ == '__main__':
  main()

with the message:

Traceback (most recent call last):
  File "a.py", line 13, in <module>
    main()
  File "a.py", line 10, in main
    g(f)
  File "a.py", line 3, in g
    f()
  File "a.py", line 8, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

But if I simply change the variable x to an array, it works:

def g(f):
  for _ in range(10):
    f()

def main():
  x = [10]
  def f():
    print x[0]
    x[0] = x[0] + 1
  g(f)

if __name__ == '__main__':
  main()

with the output

10
11
12
13
14
15
16
17
18
19

The reason I am confused is, if from f() it can't access x, why it becomes accessible if x is an array?

Thanks.

like image 350
Kan Li Avatar asked May 09 '13 01:05

Kan Li


People also ask

What is the scope of a nested function?

The scope of a nested function is inside the enclosing function, i.e. inside one of the constituent blocks of that function, which means that it is invisible outside that block and also outside the enclosing function. A nested function can access other local functions, variables, constants, types, classes, etc.

What is nested scope of variable?

The scope of a variable is the range of functions that have direct access to the variable to set, modify, or acquire its value. When you define a local (i.e., nonglobal) variable within a function, its scope is normally restricted to that function alone.

Can a nested function access outer variable?

Python Inner Functions or Nested Functions can access the variables of the outer function as well as the global variables.

Which variables are used in nested functions whose local scope is not defined?

Nonlocal variables are defined in the nested function whose scope is not defined.


1 Answers

But this answer says the problem is with assigning to x. If that's it, then printing it should work just fine, shouldn't it?

You have to understand the order in which things happen. Before your python code is even compiled and executed, something called a parser reads through the python code and checks the syntax. Another thing the parser does is mark variables as being local. When the parser sees an assignment in the code in a local scope, the variable on the lefthand side of the assignment is marked as local. At that point, nothing has even been compiled yet--let alone executed, and therefore no assignment takes place; the variable is merely marked as a local variable.

After the parser is finished, the code is compiled and executed. When execution reaches the print statement:

def main():
  x = 10     #<---x in enclosing scope

  def f():
    print x    #<-----

    x = x + 1  #<-- x marked as local variable inside the function f()

the print statement looks like it should go ahead and print the x in the enclosing scope (the 'E' in the LEGB lookup process). However, because the parser previously marked x as a local variable inside f(), python does not proceed past the local scope (the 'L' in the LEGB lookup process) to lookup x. Because x has not been assigned to in the local scope at the time 'print x' executes, python spits out an error.

Note that even if the code where an assignment occurs will NEVER execute, the parser still marks the variable on the left of an assignment as a local variable. The parser has no idea about how things will execute, so it blindly searches for syntax errors and local variables throughout your file--even in code that can never execute. Here are some examples of that:

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            a b c  #...yet the parser finds a syntax error here


    return f

f = dostuff()
f()



--output:--
File "1.py", line 8
     a b c
      ^
SyntaxError: invalid syntax

The parser does the same thing when marking local variables:

def dostuff ():
    x = 10 

    def f():
        print x

        if False:  #The body of the if will never execute...
            x = 0  #..yet the parser marks x as a local variable

    return f

f = dostuff()
f()

Now look what happens when you execute that last program:

Traceback (most recent call last):
  File "1.py", line 11, in <module>
    f()
  File "1.py", line 4, in f
    print x
UnboundLocalError: local variable 'x' referenced before assignment

When the statement 'print x' executes, because the parser marked x as a local variable the lookup for x stops at the local scope.

That 'feature' is not unique to python--it happens in other languages too.

As for the array example, when you write:

x[0] = x[0] + 1

that tells python to go lookup up an array named x and assign something to its first element. Because there is no assignment to anything named x in the local scope, the parser does not mark x as a local variable.

like image 68
7stud Avatar answered Oct 05 '22 21:10

7stud