Mastering Python's LEGB Rule: A Comprehensive Guide to Variable Scope

Introduction to Python Variable Scope

When you write Python code, every variable and function name exists within a specific scope—the region of your program where that name is accessible. Understanding how Python resolves names is crucial for avoiding bugs and writing clean, maintainable code. The LEGB rule provides a systematic way to trace this resolution process, moving from the innermost scope outward. In this article, you will explore what each letter of LEGB means, see real‑world examples, and learn how to cross scope boundaries using the global and nonlocal statements.

Mastering Python's LEGB Rule: A Comprehensive Guide to Variable Scope
Source: realpython.com

By the end, you will be able to predict how Python resolves any name reference in your code—whether it's a variable inside a function, a nested function, or a built‑in like len(). Let's dive into the scope hierarchy.

What Is the LEGB Rule?

LEGB stands for Local, Enclosing, Global, Built‑in. It defines the order Python follows when looking up a name:

  • Local – inside the current function or lambda
  • Enclosing – in any enclosing function(s) (for nested functions)
  • Global – at the module (file) level
  • Built‑in – from the set of names pre‑defined in Python (e.g., print, len)

Python searches these scopes in this exact order. If the name is not found in any of them, a NameError is raised.

Local Scope

The local scope is the innermost scope. It is created whenever a function or lambda is called. Variables defined inside a function are local to that function and cannot be accessed from outside. For example:

def my_function():
    x = 10  # local to my_function
    print(x)  # works

my_function()
# print(x)  # NameError

Every function call creates a new local scope. This means recursive calls have separate local namespaces.

Enclosing Scope

The enclosing scope comes into play with nested functions (a function defined inside another function). When Python looks up a name inside the inner function, it first checks the local scope of that inner function, then the local scope of the outer (enclosing) function, and so on. This is also known as non‑local scope.

def outer():
    y = 20
    def inner():
        print(y)  # looks in outer's local scope
    inner()

outer()

Note that Python treats y as a closed‑over variable (also called a closure) if the inner function refers to it.

Global Scope

The global scope is the top‑level scope of your module. Variables defined outside any function or class are global. They are accessible from anywhere in the module, but you must use the global keyword to reassign them inside a function (otherwise Python treats them as local).

z = 30  # global

def my_function():
    global z
    z = 40  # now modifies the global z

my_function()
print(z)  # 40

Global variables can make code harder to reason about, so use them sparingly.

Built‑in Scope

The built‑in scope is the outermost scope. It contains names like print, len, range, and TypeError. Python always looks in the built‑in scope last. You can inspect built‑ins with dir(__builtins__). Be careful not to shadow built‑ins by using the same name for your own variables—this can lead to confusing bugs.

Crossing Scope Boundaries: global and nonlocal

Sometimes you need to modify a variable that belongs to an outer scope. Python provides two statements for this:

Mastering Python's LEGB Rule: A Comprehensive Guide to Variable Scope
Source: realpython.com
  • global – tells Python that a variable inside a function refers to a global variable (even for assignment).
  • nonlocal – used inside nested functions to indicate that a variable refers to a variable in the nearest enclosing function (excluding globals).

Without these statements, assigning to a variable inside a function creates a new local variable, leaving the outer variable unchanged.

Example: Using global

counter = 0

def increment():
    global counter
    counter += 1

increment()
increment()
print(counter)  # 2

Example: Using nonlocal

def outer():
    x = 10
    def inner():
        nonlocal x
        x = 20
    inner()
    print(x)  # 20

outer()

Practical Examples of the LEGB Rule in Action

Consider this nested function that uses all scopes:

import math  # part of built‑in? No, math is not built‑in; it's a module in global scope.

PI = 3.14159  # global

def area_calculator(radius):
    # local variable 'radius'
    def inner():
        # uses nonlocal 'radius' (enclosing), global 'PI', and built‑in 'print'
        print(f"Area: {PI * radius**2}")
    inner()

area_calculator(2)

Here, Python resolves print from built‑ins, PI from globals, radius from the enclosing scope of area_calculator. No local variable shadows anything.

A common pitfall is forgetting the global or nonlocal statement when trying to reassign an outer variable. For example:

count = 0
def increment():
    # missing 'global count'
    count += 1  # Raises UnboundLocalError

increment()

Because Python sees an assignment and assumes count is local—but it hasn't been defined locally. The remedy is to add global count.

Conclusion

Understanding the LEGB rule is essential for every Python developer. It clarifies how variable names are resolved and helps you debug scope‑related errors. Remember the order: Local → Enclosing → Global → Built‑in. Use global and nonlocal judiciously to modify outer scopes, and always consider whether a variable should be local, global, or enclosed.

Now that you've seen the theory and practice, you can confidently navigate Python's scoping rules in your projects. Happy coding!

Tags:

Recommended

Discover More

6 Key Insights into Aave's $575M Milestone on MegaETH After MEGA Token LaunchThe End of the PHP License: What You Need to Know10 Essential Strategies for Securing Identity in an Era of Humans, Machines, and AIiOS 27 Overhauls Camera with Customizable Controls, Refines Weather, Safari, and Interface DesignHow a GTA 4 Mod Revives Call of Duty's Classic Zombie Mode After 18 Years