Fundamentals 12 min read

Master Python’s LEGB Rule: Scope Secrets for Faster, Safer Code

This article explains Python’s LEGB rule—Local, Enclosing, Global, Built‑in scopes—detailing each namespace with clear code examples, demonstrating name resolution order, performance impacts, and practical best‑practice tips such as minimizing globals, using nested scopes, and avoiding built‑in overrides.

Code Mala Tang
Code Mala Tang
Code Mala Tang
Master Python’s LEGB Rule: Scope Secrets for Faster, Safer Code

1. What is the LEGB rule in Python?

Python, like most languages, defines several scopes for variables and functions. The LEGB rule (Local, Enclosing, Global, Built‑in) determines the order in which Python searches these namespaces.

1.1 Local scope

A variable defined inside a function is local and cannot be accessed from outside the function.

def func():
    local_var = 10
    print(local_var)

Use locals() inside a function to inspect its local namespace.

def func():
    local_var = 10
    print("Local Scope:", locals())
func()

1.2 Enclosing scope

An enclosing scope exists only for nested functions; the inner function can access variables from the outer function.

def outer_func():
    outer_var = 20
    def inner_func():
        print(outer_var)
    inner_func()

1.3 Global scope

Variables defined at the top level of a script are in the global scope and are accessible from any function.

global_var = 30

def func():
    print(global_var)

1.4 Built‑in scope

The built‑in scope contains objects provided by Python itself, such as len() and print(). These are always available without any import.

2. Name resolution order

When Python encounters a name, it searches the namespaces in the order: Local → Enclosing → Global → Built‑in.

a = "Global"

def outer():
    a = "Enclosing"
    def inner():
        a = "Local"
        print("a - inner:", a)
    inner()
    print("a - outer:", a)

outer()
print("a - global:", a)

The built‑in scope is consulted last. The following example shadows the built‑in print and then restores it from globals()['__builtins__']:

def test_print():
    print = "Shadowing built-in"
    try:
        print("This will throw an error because 'print' is now a string, not a function")
    except TypeError as e:
        print = globals()['__builtins__'].print
        print("Caught error:", e)

test_print()

3. Impact on performance and memory

Using local variables is faster than globals. The following benchmark shows a ~30% speed‑up when the computation uses a local variable.

sum_global = 0

def sum_numbers_global():
    global sum_global
    for number in range(1000000):
        sum_global += number

def sum_numbers_local():
    local_sum = 0
    for number in range(1000000):
        local_sum += number
    return local_sum

List comprehensions also benefit from LEGB because the loop variable is local, making them faster than an equivalent for loop.

# for loop version
factor = 2
results = []
for i in range(100):
    results.append(i * factor)

# list comprehension version
factor = 2
results = [i * factor for i in range(100)]

4. Design insights and best practices

Minimize use of the global namespace . Prefer passing data through functions or encapsulating state in classes.

items = []

def add_item(item):
    items.append(item)

def clear_items():
    global items
    items.clear()

Encapsulating the list in a class avoids accidental modifications and improves maintainability.

class ItemManager:
    def __init__(self):
        self.items = []
    def add_item(self, item):
        self.items.append(item)
    def clear_items(self):
        self.items = []

manager = ItemManager()
manager.add_item("apple")
manager.add_item("banana")
print(manager.items)
manager.clear_items()
print(manager.items)

When to use nested namespaces?

Nested scopes are useful for creating counters or decorators without resorting to globals.

def call_counter(func):
    def wrapper(*args, **kwargs):
        wrapper.count += 1
        return func(*args, **kwargs)
    wrapper.count = 0
    return wrapper

@call_counter
def function_to_track():
    pass

function_to_track()
function_to_track()
print("Function was called", function_to_track.count, "times.")

Avoid overriding built‑in objects

Never overwrite built‑in functions such as print or len, as this can cause confusing bugs.

Summary

Understanding the LEGB rule clarifies Python’s name‑resolution order, helps write more efficient code, and guides better design decisions such as reducing globals, leveraging nested scopes, and protecting built‑in objects.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancebest practicesNamespacescopeLEGB
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.