Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing variables defined in enclosing scope

Tags:

From the Google Style Guide on lexical scoping:

A nested Python function can refer to variables defined in enclosing functions, but can not assign to them.

Both of these seem to check out at first:

# Reference def toplevel():     a = 5     def nested():         print(a + 2)     nested()     return a toplevel() 7 Out[]: 5  # Assignment def toplevel():     a = 5     def nested():         a = 7 # a is still 5, can't modify enclosing scope variable     nested()     return a toplevel() Out[]: 5 

So why, then, does a combination of both reference and assignment in the nested function lead to an exception?

# Reference and assignment def toplevel():     a = 5     def nested():         print(a + 2)         a = 7     nested()     return a toplevel() # UnboundLocalError: local variable 'a' referenced before assignment 
like image 564
Brad Solomon Avatar asked Sep 02 '17 22:09

Brad Solomon


People also ask

What is an enclosing scope in Python?

Enclosing (or nonlocal) scope is a special scope that only exists for nested functions. If the local scope is an inner or nested function, then the enclosing scope is the scope of the outer or enclosing function. This scope contains the names that you define in the enclosing function.

Is enclosed a variable scope?

Enclosing Scope in Python Of course, a python variable scope that isn't global or local is nonlocal. This is also called the enclosing scope.

Which keyword is used for enclosed variable modification?

If we want to change the enclosing variable in an inner function, a nonlocal keyword is required. If we want to change a global variable in a function, a global keyword is required.


2 Answers

In first case, you are referring to a nonlocal variable which is ok because there is no local variable called a.

def toplevel():     a = 5     def nested():         print(a + 2) # theres no local variable a so it prints the nonlocal one     nested()     return a 

In the second case, you create a local variable a which is also fine (local a will be different than the nonlocal one thats why the original a wasn't changed).

def toplevel():     a = 5      def nested():         a = 7 # create a local variable called a which is different than the nonlocal one         print(a) # prints 7     nested()     print(a) # prints 5     return a 

In the third case, you create a local variable but you have print(a+2) before that and that is why the exception is raised. Because print(a+2) will refer to the local variable a which was created after that line.

def toplevel():     a = 5     def nested():         print(a + 2) # tries to print local variable a but its created after this line so exception is raised         a = 7     nested()     return a toplevel() 

To achieve what you want, you need to use nonlocal a inside your inner function:

def toplevel():     a = 5     def nested():         nonlocal a         print(a + 2)         a = 7     nested()     return a 
like image 152
Mohd Avatar answered Sep 17 '22 09:09

Mohd


For anyone stumbling across this question, in addition to the accepted answer here, it is answered concisely in the Python docs:

This code:

>>> x = 10 >>> def bar(): ...     print(x) >>> bar() 10 

works, but this code:

>>> x = 10 >>> def foo(): ...     print(x) ...     x += 1 

results in an UnboundLocalError.

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.

In the example above you can access the outer scope variable by declaring it global:

>>> x = 10 >>> def foobar(): ...     global x ...     print(x) ...     x += 1 >>> foobar() 10 

You can do a similar thing in a nested scope using the nonlocal keyword:

>>> def foo(): ...    x = 10 ...    def bar(): ...        nonlocal x ...        print(x) ...        x += 1 ...    bar() ...    print(x) >>> foo() 10 11 
like image 40
Brad Solomon Avatar answered Sep 21 '22 09:09

Brad Solomon