The LEGB rule in Python

Short answer:

When we use a variable name, how does Python know where to find its value? It uses the LEGB rule!

LEGB stands for Local, Enclosing, Global, Built-in. These are the possible scopes of a variable in Python. When we use a variable name in Python, it follows this sequence to find its value.

LEGB rule | Scope resolution in Python

Have you ever tried accessing a variable in Python and encountered a NameError, even though you're sure you defined it somewhere? Or maybe you’ve accidentally modified a global variable inside a function, leading to unexpected bugs? These challenges often arise from confusion about variable scope.

To resolve such issues, Python uses the LEGB rule to determine where a variable's value comes from. The LEGB rule outlines the order in which Python searches for variables, helping us understand and control how our code accesses or modifies them.

In Python, variables are stored in namespaces, which act as containers for mapping names to objects. When Python encounters a variable, it searches through different namespaces to find its value, following the LEGB rule.

  • L - Local:: Variables defined within the current function.

  • E - Enclosing: Variables in the local scope of any enclosing functions (for nested functions).

  • G - Global: Variables defined at the module level (outside any function or class).

  • B - Built-in: Names pre-defined in Python, such as len() or dict.

Python searches in the order Local → Enclosing → Global → Built-in until it finds the variable. If the variable isn't found, it raises a NameError.

Namespaces

A namespace is essentially a container that holds variable names and their corresponding objects. Think of it as a mapping between names and objects. In Python, every time a function or a block of code is executed, a new namespace is created. When the block finishes, the namespace is destroyed.

The following code snippet will create a namespace that contains a variable with name x and value 3. After the program finishes running, this namespace is deleted.

x = 3

Let's look at a diagram that represents a namespace. The namespace is drawn as a big box containing variable names with arrows pointing to objects (boxes). Inside each box is a value(s). An arrow from a variable name to a box indicates that the name maps to the object that the box represents.

The code snippet above could be represented in a diagram like this:

A namespace that contains a variable with name x and value 3
A namespace that contains a variable with name x and value 3

Namespaces can also be nested. Calling a function creates a new namespace:

def f(x):
return x
y = f(3)

Look at the illustration below to see the namespaces and the objects they contain in the example above. Notice that function parameters are stored in that function's namespace when it is created (i.e., when the function is called).

Line 1
1 of 4

The namespace that contains a variable determines when that variable is in scope, that is, when it can be used.

Now, let's break down each scope with examples.

Understanding local and global scopes

x = 5 # Global variable
def f(x):
print(f"The value of x is {x}") # Local scope
f(3)

In this example, x = 5 exists in the global namespace, and when we call the function f(3), a new local scope is created inside the function. The argument 3 gets assigned to the local x, overriding the global x inside the function. Thus, Python prints "The value of x is 3" because it checks the local scope first.

Try it yourself:

  1. Try changing the argument passed to the function: What happens if you pass f(7) instead of f(3)? Predict the output before running the code.

  2. Modify the global variable x: What happens if you change x = 5 to x = 10 outside the function and then call f() again with different values?

  3. Remove the parameter in the function definition: What will happen if you modify the function f() to remove the parameter x entirely? How will the global x behave in this case?

The enclosing scope

x = 5 # Global scope
def outer():
x = 10 # Enclosing scope
def inner():
print(f"The value of x is {x}") # Refers to enclosing scope
inner()
outer()

In this example, x = 10 is in the enclosing scope of outer(), and the inner() function has access to it. When we call inner(), Python looks for x in the local scope first (but doesn't find it) and then checks the enclosing scope, printing "The value of x is 10".

Try yourself:

  1. Try changing the value of x in the enclosing scope: What will happen if you modify the value of x inside outer() (for example, set x = 15)? How does it affect the output of inner()? In this case, the variable x in outer() is in the enclosing scope, which means inner() can access it.

  2. Modify the function call sequence: What happens if you call outer() more than once or change the nesting of the functions? How does the scope resolution change in these scenarios?

  3. Add a global variable: Try adding x = 20 in the global scope and see how Python resolves x when you add another print statement after calling inner().

NameError in the absence of a variable

Look at the example below:

def f():
print(x)
f() # Raises NameError

Since x is not defined in the local, enclosing, global, or built-in scopes, Python raises a NameError.

Built-in scope

This scope contains all built-in functions and variables like len(), dict(), and print().

print(len("hello")) # 'len' is a built-in function

Python follows the LEGB rule, searching for variables in the Local, Enclosing, Global, and Built-in scopes in that order.

Managing Python namespaces and scopes

In Python, understanding namespaces and scope-related keywords like globals(), locals(), global, and nonlocal can help with debugging and modifying variables across different scopes.

Namespace dictionaries

Sometimes you might want to inspect all the current variables in a given namespace. Python offers built-in functions for this:

  • globals(): Returns a dictionary of all the global variables.

  • locals(): Returns a dictionary of local variables within the current function or scope.

These are useful tools for debugging or gaining visibility into what objects are currently in a namespace. Additionally, globals() can be used to dynamically access or modify global variables, though this should be done cautiously to avoid unintended side effects.

Scope keywords: global and nonlocal

There are cases where you need to access or modify variables in different namespaces explicitly, which can be done with the global and nonlocal keywords.

For example, suppose you want to modify a global variable inside a function:

x = 5
def f():
# This new value of `x` will persist only
# while the namespace of f still exists
x = 3
print(f'Before: the value of x is {x}')
f()
print(f'After: the value of x is {x}')

We can avoid this by using the keyword global before the variable name. Declaring global x will let us use the name x as if we were in the global namespace.

x = 5
def f():
# Declaring x as global allows us to modify
# its value in the global namespace
global x
x = 3
print(f'Before: the value of x is {x}')
f()
print(f'After: the value of x is {x}')

To access a variable in the closest enclosing namespace, we can use the keyword nonlocal. For example:

def outer():
x = 5
def inner():
nonlocal x
x = 3 # modifies `x` in the enclosing `outer` function
inner()
print(x) # prints 3
outer()

In this case, nonlocal allows us to modify x in the outer function’s scope, not in the global scope.

However, using these keywords is generally considered bad practice. It is confusing to read code that uses objects outside of a modularized portion.

Actions that create scope in Python

The following actions create new scopes in Python:

  • Calling a function: Each function call creates a new local scope.
  • Accessing attributes or functions of a class: Class and instance scopes are created when you access or define functions within them.
  • Accessing objects in another module or package: Imported modules reside in the global scope of the importing module but create their own namespace, similar to calling a function.

Actions that DON’T create scope in Python

The following actions do not create new scopes:

  • Entering an if statement: Variable assignments within an if block affect the current scope.

  • Entering a for or while loop: Variables defined within loops also stay in the current scope.

  • Creating a context manager (using with statements): These do not create a new scope either.

Test your understanding of the LEGB rule

1

What will the following code print?

x = 50  # Global scope

def outer():
    x = 30  # Enclosing scope
    def inner():
        x = 10  # Local scope
        print(x)
    inner()

outer()
A)

50

B)

30

C)

10

D)

NameError

Question 1 of 20 attempted

Exercise: Fix the scope

Modify the following code so that the outer() function changes the global value of x to 100 while keeping inner()'s x value as 50.

x = 10 # Global scope
def outer():
x = 50 # Enclosing scope
def inner():
print(f"Inner x: {x}")
inner()
outer()
print(f"Global x: {x}")

Key takeaways

  • LEGB rule: Python follows a specific order for resolving variable names—Local, Enclosing, Global, and Built-in—known as the LEGB rule.

  • Scope creation: Scopes are defined within functions, classes, and modules, and they help isolate variables in different contexts.

  • Name isolation: When variables with the same name exist in different scopes, their values are isolated and only accessible within their specific scope.

  • Modifying variables: Changing a variable in one scope does not affect the same-named variable in another scope.

  • global and nonlocal keywords: While generally discouraged, these keywords can be used to modify variables in higher scopes.

Become a Python Developer with Our Comprehensive Learning Path!

Ready to kickstart your career as a Python Developer? Our Become a Python Developer path is designed to take you from your first line of code to landing your first job.

This comprehensive journey offers essential knowledge, hands-on practice, interview preparation, and a mock interview, ensuring you gain practical, real-world coding skills. With our AI mentor by your side, you’ll overcome challenges with personalized support, building the confidence needed to excel in the tech industry.

Frequently asked questions

Haven’t found what you were looking for? Contact Us


What happens if Python doesn’t find a variable in any scope?

Python will raise a NameError, indicating that the variable is not defined in any scope (Local, Enclosing, Global, or Built-in).


Can I modify a global variable inside a function?

Yes, you can modify a global variable inside a function using the global keyword. Without it, Python treats the variable as local by default, even if it shares the same name as a global variable.


How is LEGB different from variable shadowing?

Variable shadowing occurs when a local variable has the same name as a global variable. LEGB ensures that the local variable takes precedence, effectively “hiding” the global one in that scope.


What are the risks of overusing global and nonlocal keywords?

Overusing global and nonlocal can make code harder to debug and maintain due to:

  • Reduced readability: Changes to these variables are harder to track.
  • Unintended side effects: Variables can be modified in unexpected ways.
  • Tight coupling: Functions become less reusable.

Best practice: Use these keywords sparingly. Prefer returning values or passing parameters to maintain clarity and modularity.


Free Resources