Python scope resolution is based on LEGB rule, a shorthand for Local, Enclosing, Global, Built-in. Though it looked simple, it was a little confusing to me at the beginning, for example, consider the following example:
x = 10 def add(): x += 1 print(x)
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
For the above example, I have expected the code to work fine by taking the value 10 for x and then performing the add() function. Unfortunately, it returned an UboundLocalError with x referenced before the assignment. So I decided to make a different try:
x = 10 def add(): print(x)
And it worked right!!
In one of the above code snippets, the global variable x was not recognised and returned an UnboundLocalError however when we tried to just print the variable it worked fine.
The reason is to do with scoping. When we make an assignment to a variable inside a python function, the variable becomes local to that scope of the function and shadows the global variable with the same name. Hence, in example 1 the global variable x is not visible inside the function add() and returned an UnboundLocalError.
In example 2, since no assignment is done to x, the global x is visible inside the scope of function and is returned successfully by the function.
Luckily, python itself provides us with an alternative option for example 1 to assign a global variable inside a function scope by using keywords global/nonlocal.
By using nonlocal/global keywords, allows the function to access the variable described in the outer scope of the function.
Though both global and nonlocal look similar, both serve different sets of functionalities.
Using the global keyword allows the inner scope to access variables declared in the global scope of the function, or in other words, allows access to variables that are not defined inside any of the functions
x = 10 def increment(): global x x += 1 print(x)
Whereas, nonlocal allows inner scope to access variables defined in the parent scope of the function.
x = 10 def increment(): nonlocal x x += 1 print(x)
In the above example, before initializing the variable x inside the function increment(), we explicitly told python not to initialize a new variable x, instead use the nonlocal variable x defined inside the function add(). So when the interpreter performs the function increment(), it accesses the value 10 defined in the function add() and then increments it to 11 and the error is avoided.