DEV Community

Cover image for Scope and LEGB: Collaboration in Python
Ruqiya Arshad
Ruqiya Arshad

Posted on • Edited on

Scope and LEGB: Collaboration in Python

Scope in Python is an inner system of rules in a namespace that has variable visibility rules. Variable visibility means how the variable can be seen, referenced, or accessed. Before mentioning and discussing the rules, we should discuss the types of scope in the system.

  1. Local Scope: is inside the function.
  2. Global Scope: is present in the module.
  3. Enclosing Scope: is present in the outer function and its inner nested functions.
  4. Built-In Scope: is present in language-based designed functions.

Now prior to jumping to the sequence of LEGB, let’s discuss rules of scope first. These scope rules check where you can observe and use the variable:

  • Rule 1: The variable named inside a function cannot be used outside the function.
def citizen_identity():
    cnic_number = "35200-000000-1" #Local variable.
    print("The citizen identity card number is " + cnic_number + ".")
citizen_identity()#It prints only when below code line is removed.
                  #Output: "The citizen identity card number is 35200-000000-1."
print(cnic_number)#Error! as the local variable is used outside the function.
                  #Output: "NameError: name 'cnic_number' is not defined"
Enter fullscreen mode Exit fullscreen mode

In the Rule 1 example, we see that when the local variable cnic_number is accessed globally by print outside the function, then it will give a NameError as shown in the code.
For better understanding, you can try the example in your code environment.

  • Rule 2: Global variables can be accessed inside a local scope and modified by using the “global” keyword.
cnic_number = "35200-000000-0" #Global variable

def citizen_identity():
    global cnic_number #Global keyword indicates that no new variable is declared with the same name.
    cnic_number = "35200-000000-1" #Modifying the global variable value at output.
    print("The citizen identity card number is " + cnic_number + ".")
citizen_identity()#Output: "The citizen identity card number is 35200-000000-1."
Enter fullscreen mode Exit fullscreen mode

In the Rule 2 example, the global keyword concept is explained. In Python, when two variables exist with the same name, then they can cause confusion, and also the shadow effect occurs (explained in Rule 4). When we use the global keyword with the local variable cnic_number, then it tells the interpreter that no new variable is declared locally, or we are using the global variable inside the function whose value can also be modified there.

-Rule 3: Function-to-function, the local variables cannot be reached or accessed.

def citizen_identity():
    cnic_number = "35200-000000-1"
    print("The citizen identity card number is " + cnic_number + ".")
citizen_identity()

def citizen_nationality():
    user_input = input("Mention your nationality here. ") #The program works fine upto here.
    nationality = ("Pakistani", "Kuwaiti", "Saudi Arabian")
    if user_input in nationality:
        print(cnic_number) #Error! "NameError: name 'cnic_number' is not defined".
                           #Local variable from another function cannot be used.
    else:
        print("Metioned nationality is not in the list!")
citizen_nationality()
Enter fullscreen mode Exit fullscreen mode

In the Rule 3 example, it explains the variable declared inside the local scope of one function cannot be accessed by another function's local scope. When in the first function citizen_identity(), the declared variable cnic_number is accessed by the other function named citizen_nationality(), then the runtime error occurs, as shown in the code block.

-Rule 4: Both global and local scopes can have the same variable names, but the local variable causes a shadowing effect, making the global variable inaccessible.

cnic_number = "35200-000000-0" 

def citizen_identity():
    cnic_number = "35200-000000-1" #Local variable treated here as a new variable.
    print("The citizen identity card number is " + cnic_number + ".")
citizen_identity()
print(cnic_number)#Output: 35200-000000-0
                  #This shows that the global variable value is not changed.
Enter fullscreen mode Exit fullscreen mode

In the Rule 4 example, the shadowing effect of the local variable is explained. When both local and global have the same variable names, then the local variable value shadows the global variable.
When the print(cnic_number) statement is used inside the function, then it will print the value of the local variable as 35200-000000-1; however, when print(cnic_number) is used globally, then it prints 35200-000000-0.It shows that global value remains unchanged. To show the shadow effect on the global variable, let's reuse this code snippet as follows:

cnic_number = "35200-000000-0" 

def citizen_identity():
    cnic_number = "35200-000000-1" #Local variable treated here as a new variable.
    print("The citizen identity card number is " + cnic_number + ".")
citizen_identity() #Output: "The citizen identity number is 35200-000000-1"
Enter fullscreen mode Exit fullscreen mode

In this code snippet we see that the local variable shadows the global variable value at the output. Instead of, global variable value, 35200-000000-0 the local variable value, 35200-000000-1 is printed at the output. Making it inaccessible.

How scope rules are implemented can only be understood by coding practice.

The question arises: why does the local scope have preference first, then the global scope? The answer lies in the LEGB sequence, which will be discussed now.

The LEGB sequence (Local, Enclosing, Global, Built-In) is a blueprint in Python that locates variables in the scopes of namespaces when referenced in the code. For a smooth LEGB search of variables, the scope rules should be followed while coding, or else the errors will occur in the runtime.
Let’s understand the LEGB sequence in a practical manner.

The below example explains the sequence in which LEGB (Local->Enclosing->Global->Built-In) searches for the referenced variables in all four scopes in Python. We will use the same code examples as above in this article but with a bit of change by adding a nested function in an enclosing scope. Point to keep in mind: LEGB search or lookup begins when the variable is referenced during execution because we often get confused at the variable declaration. And when LEGB finds the variable, it stops searching for that specific referenced variable, and then that variable is used.

cnic_number = "35200-000000-0" #global variable
def citizen_identity():
    cnic_number = "35200-000000-1" #local variable
    print("The citizen identity card number is " + cnic_number + ".") #LEGB search starts. Local(found)
citizen_identity()

print(cnic_number)#LEGB Lookup starts. Global(Found)

def citizen_nationality(): #enclosing scope (outer)
    user_input = input("Mention your nationality here...") #local to outermost enclosing scope
    nationality = ("Pakistani", "Kuwaiti", "Saudi Arabian")#local variable

    if user_input in nationality: #LEGB variable lookup.
        print(cnic_number)#global variable from the module level. Local(not found)->Enclosing(not active)->Global(found)
    else:
        print("Metioned nationality is not in the list!")

    def citizen_name(): #inner enclosing scope
        name_input = input("Mention your name...")#local variable
        print(name_input) # LEGB Lookup.Local(found)
        print(user_input) #non-local variable. LEGB Lookup. Enclosing(found)
    citizen_name()

citizen_nationality()

Enter fullscreen mode Exit fullscreen mode

In the above code example, when the local variable is referenced inside the function, LEGB starts finding it first in the local scope. We need to remind ourselves every time that LEGB works in a sequence, but code structure also matters. If there is no local scope, then in the LEGB sequence, it simply is not active. Coming back to the example, when LEGB finds the referenced variable, cnic_number = 35200-000000-1, it stops and uses that variable to print it at runtime. Similarly, when a global variable is referenced, the LEGB search starts. As the first "local" and second priority scope, "enclosing scope," simply do not exist in the code structure, so it moves towards the third priority, "global scope," where it is found. LEGB stops and uses that variable.

In the second part of code,
The enclosing scope citizen_nationality() has a nested function, citizen_name(). Now, in this code structure, we will see how the LEGB sequence works for enclosing scope. It searches for referenced variables from the innermost to outermost enclosing scope. As we see that in a nested innermost function when the local variable of the enclosing outermost function user_input is referenced (which is non-local to the innermost function), then the LEGB lookup begins and it is found in the enclosing scope.

To understand the LEGB sequence, the small exercise will help you. Try to design a code structure that includes LEGB search for built-in scope. (Hint: Try using len())

Top comments (0)