PythonProgramming Intermediate

Python Scope & LEGB Rule: Mastering Variable Visibility

TT
TopicTrick
Python Scope & LEGB Rule: Mastering Variable Visibility

Python LEGB Rule: Quick Answer

Python resolves variable names using the LEGB rule: it searches Local scope first, then Enclosing (outer function) scope, then Global (module-level) scope, and finally Built-in scope. The first match wins. Understanding this lookup order prevents NameError bugs and helps you write clean, modular code.

Introduction to Python Scope

In programming, Scope refers to the region of a program where a specific variable is accessible. Simply put, it determines the "visibility" of your variables and functions.

Understanding scope is crucial for avoiding Name Collisions (where two different parts of your code use the same name for different purposes) and for writing clean, modular code.


What is a Namespace?

Before diving into scope, we must understand Namespaces. A namespace is essentially a dictionary where keys are variable names and values are the objects themselves.

Python uses namespaces to keep track of variables in different areas of your code. For example, the os module has its own namespace, allowing you to have a variable named getcwd in your script without conflicting with os.getcwd().


The LEGB Rule: Python’s Lookup Strategy

When you reference a variable, Python searches for it in a specific order. This is known as the LEGB Rule:

  1. L: Local Scope — Inside the current function or lambda.
  2. E: Enclosing Scope — Inside any enclosing (outer) functions.
  3. G: Global Scope — At the top level of the script or module.
  4. B: Built-in Scope — Reserved keywords and built-in functions like len, int, print.

1. Local (Function) Scope

Variables defined inside a function belong to that function’s local scope. They are created when the function is called and destroyed when it returns.

python

2. Enclosing (Nonlocal) Scope

This occurs in nested functions. The "outer" function’s local scope becomes the "enclosing" scope for the "inner" function.

python

3. Global (Module) Scope

The global scope is the top-most level of your Python script. Variables defined here are visible throughout the entire file.

python

4. Built-in Scope

This is the widest scope, containing all of Python’s built-in objects. It’s always available.

Avoid Shadowing

Never name your variables after built-in functions (e.g., `list = [1, 2]`). Doing so 'shadows' the built-in name, making the original `list()` function inaccessible in that scope!


    Modifying Scope: global & nonlocal

    Normally, you can read variables from outer scopes, but you cannot modify them. To do so, you need special keywords.

    The global Keyword

    Used to modify a variable defined at the top level of your script from within a function.

    python

    The nonlocal Keyword

    Used in nested functions to modify a variable in the enclosing (outer) function.

    python

    Python Scope Cheat Sheet

    OperationLocal CodeEnclosed CodeGlobal CodeBuilt-in
    Access Local
    Modify Local
    Access Enclosing
    Modify Enclosingnonlocal
    Access Global
    Modify Globalglobalglobal
    Access Built-in

    LEGB in Action: A Complete Example

    Here is a single example that demonstrates all four scopes simultaneously:

    python

    Output:

    text

    Each scope is completely isolated unless you explicitly use global or nonlocal.

    Common Scope Mistakes (And How to Fix Them)

    Mistake 1: UnboundLocalError

    python

    As soon as Python sees count += 1 inside a function, it assumes count is a local variable — even before the assignment. This is why you get UnboundLocalError.

    Mistake 2: Shadowing Built-ins

    python

    Never name your variables after Python built-ins like list, dict, str, len, id, type, or input.

    Closures: Practical Use of Enclosing Scope

    A closure is an inner function that "closes over" variables from its enclosing scope, preserving them even after the outer function has returned:

    python

    Closures are the foundation of decorators in Python — a powerful pattern for modifying function behaviour.

    Related Python Topics

    For the official language specification, see Python Naming and Binding documentation and the global statement.

    Conclusion

    Mastering the LEGB rule and knowing when to use global or nonlocal is a hallmark of an intermediate Python developer. It allows you to build complex, nested architectures without losing track of your data!

    Up Next

    Ready for more? Let's explore Python Inheritance, where we apply these scoping rules to Class hierarchies.

      Common Mistakes with Python Scope and LEGB

      1. Modifying a global variable without the global keyword Assigning to a name inside a function creates a new local variable — it does not modify the global. If your function reads a global without assigning to it, global is unnecessary. If it assigns to it, declare global counter before the assignment. Forgetting this produces an UnboundLocalError when the function tries to read the variable before the local assignment is reached. See the Python scope documentation.

      2. Overusing global state Functions that rely on global variables are harder to test and reason about because their behaviour depends on external state that may change at any point. Prefer passing values as arguments and returning results explicitly. Reserve module-level globals for true constants (all-caps by convention: MAX_RETRIES = 3).

      3. Confusing nonlocal and global nonlocal reaches into the nearest enclosing function scope (one level up in nested functions). global reaches all the way to the module level. Using global when you meant nonlocal in a nested function silently creates or modifies a module-level variable instead of the enclosing function's variable. Always pick the narrowest scope that solves the problem.

      4. Variable shadowing built-ins Naming a variable list, dict, input, id, or type shadows the built-in with the same name in every line of code that follows within that scope. The original built-in becomes inaccessible without using builtins.list. Python will not warn you about this. Use descriptive names (user_list, config_dict) instead of single-word names that collide with built-ins. The builtins module lists every name at risk.

      5. Loop variable leaking into enclosing scope In Python, for loops do not create their own scope. After for i in range(5):, the variable i remains accessible in the enclosing scope with the value from the last iteration. This is intentional but surprises developers from C, Java, or JavaScript backgrounds. Be explicit if you need a loop variable to remain: assign it before the loop or use a separate name.

      Frequently Asked Questions

      What is the LEGB rule in Python? LEGB stands for Local, Enclosing, Global, and Built-in — the four scopes Python searches in order when resolving a name. Local is the current function body. Enclosing covers any outer functions in a closure. Global is the module level. Built-in is the builtins module containing names like print, len, and range. Python searches each scope in order and uses the first match it finds. The Python execution model documentation describes the full scoping rules.

      When should I use nonlocal in Python? Use nonlocal inside a nested function when you need to assign to a variable defined in the enclosing function's scope. The classic use case is a closure that accumulates state across multiple calls: a counter or accumulator function where the inner function must modify the outer function's variable. If you only read the outer variable (without assigning to it), nonlocal is not required. See PEP 3104 — Access to Names in Outer Scopes for the design rationale.

      Why does Python's for loop variable persist after the loop ends? Python's design philosophy keeps the language simple — adding block scope for for loops would require a new scoping rule distinct from function scope. The trade-off is that loop variables persist, which is occasionally useful (the last matched value in a search loop) but surprising to developers from block-scoped languages. If you want loop variable isolation, wrap the loop in a function or use a comprehension ([x for x in items] does not leak x in Python 3). The Python FAQ on scoping addresses this directly.