Python Scope

Learn about variable scope in Python and how Python resolves variable names using the LEGB rule.

Python Scope

A variable is only available from inside the region it is created. This is called scope.

Local Scope

A variable created inside a function belongs to the local scope of that function, and can only be used inside that function.

Example - A variable created inside a function is available inside that function:

def myfunc():
    x = 300
    print(x)

myfunc()

Function Inside Function

As explained in the example above, the variable x is not available outside the function, but it is available for any function inside the function:

Example - The local variable can be accessed from a function within the function:

def myfunc():
    x = 300
    def myinnerfunc():
        print(x)
    myinnerfunc()

myfunc()

Global Scope

A variable created in the main body of the Python code is a global variable and belongs to the global scope.

Global variables are available from within any scope, global and local.

Example - A variable created outside of a function is global and can be used by anyone:

x = 300

def myfunc():
    print(x)

myfunc()

print(x)

Naming Variables

If you operate with the same variable name inside and outside of a function, Python will treat them as two separate variables, one available in the global scope (outside the function) and one available in the local scope (inside the function):

Example - The function will print the local x, and then the code will print the global x:

x = 300

def myfunc():
    x = 200
    print(x)

myfunc()

print(x)

Global Keyword

If you need to create a global variable, but are stuck in the local scope, you can use the global keyword.

The global keyword makes the variable global.

Example - If you use the global keyword, the variable belongs to the global scope:

def myfunc():
    global x
    x = 300

myfunc()

print(x)

Also, use the global keyword if you want to make a change to a global variable inside a function.

Example - To change the value of a global variable inside a function, refer to the variable by using the global keyword:

x = 300

def myfunc():
    global x
    x = 200

myfunc()

print(x)

Nonlocal Keyword

The nonlocal keyword is used to work with variables inside nested functions.

The nonlocal keyword makes the variable belong to the outer function.

Example - If you use the nonlocal keyword, the variable will belong to the outer function:

def myfunc1():
    x = "Jane"
    def myfunc2():
        nonlocal x
        x = "hello"
    myfunc2()
    return x

print(myfunc1())

LEGB Rule

Python follows the LEGB rule to resolve variable names:

  • L - Local (inside the current function)
  • E - Enclosing (in any outer function)
  • G - Global (at the module level)
  • B - Built-in (in the built-in namespace)

Example - LEGB rule demonstration:

# Built-in scope (B)
# print, len, str, etc. are built-in functions

# Global scope (G)
x = "global x"

def outer_function():
    # Enclosing scope (E)
    x = "enclosing x"
    
    def inner_function():
        # Local scope (L)
        x = "local x"
        print(f"Inner function: {x}")  # Local x
    
    def inner_function2():
        print(f"Inner function 2: {x}")  # Enclosing x
    
    inner_function()
    inner_function2()
    print(f"Outer function: {x}")  # Enclosing x

outer_function()
print(f"Global: {x}")  # Global x

Scope Examples

Example 1: Variable Shadowing

name = "Global Alice"

def greet():
    name = "Local Bob"  # This shadows the global variable
    print(f"Hello, {name}!")

def greet_global():
    print(f"Hello, {name}!")  # Uses global variable

greet()        # Hello, Local Bob!
greet_global() # Hello, Global Alice!
print(name)    # Global Alice

Example 2: Modifying Global Variables

counter = 0

def increment():
    global counter
    counter += 1
    print(f"Counter: {counter}")

def increment_local():
    counter = 10  # Creates a new local variable
    counter += 1
    print(f"Local counter: {counter}")

print(f"Initial counter: {counter}")  # 0
increment()                           # Counter: 1
increment()                           # Counter: 2
increment_local()                     # Local counter: 11
print(f"Final counter: {counter}")    # 2

Example 3: Nested Functions and Nonlocal

def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    def decrement():
        nonlocal count
        count -= 1
        return count
    
    def get_count():
        return count
    
    return increment, decrement, get_count

# Create counter functions
inc, dec, get = make_counter()

print(get())  # 0
print(inc())  # 1
print(inc())  # 2
print(dec())  # 1
print(get())  # 1

Class Scope

Classes have their own scope rules. Class variables are shared among all instances:

Example - Class and instance variables:

class MyClass:
    class_var = "I'm a class variable"
    
    def __init__(self, name):
        self.instance_var = name  # Instance variable
    
    def show_vars(self):
        print(f"Class variable: {MyClass.class_var}")
        print(f"Instance variable: {self.instance_var}")
    
    def modify_class_var(self):
        MyClass.class_var = "Modified class variable"
    
    @classmethod
    def class_method(cls):
        print(f"Accessing class variable from class method: {cls.class_var}")
    
    @staticmethod
    def static_method():
        print("Static method - no access to class or instance variables")

# Usage
obj1 = MyClass("Object 1")
obj2 = MyClass("Object 2")

obj1.show_vars()
obj2.show_vars()

print("\nAfter modifying class variable:")
obj1.modify_class_var()
obj1.show_vars()
obj2.show_vars()  # Both objects see the change

MyClass.class_method()
MyClass.static_method()

Scope and Loops

In Python, loop variables have function scope, not block scope:

Example - Loop variable scope:

def demonstrate_loop_scope():
    # Loop variables persist after the loop
    for i in range(3):
        x = i * 2
    
    print(f"i after loop: {i}")  # i is still accessible
    print(f"x after loop: {x}")  # x is still accessible
    
    # List comprehension has its own scope
    squares = [y**2 for y in range(5)]
    # print(y)  # This would cause an error - y is not accessible
    
    return squares

result = demonstrate_loop_scope()
print(f"Squares: {result}")

Practical Examples

Example 1: Configuration Manager

class ConfigManager:
    _instance = None
    _config = {}
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def set_config(self, key, value):
        ConfigManager._config[key] = value
    
    def get_config(self, key, default=None):
        return ConfigManager._config.get(key, default)
    
    def show_all_config(self):
        for key, value in ConfigManager._config.items():
            print(f"{key}: {value}")

# Global configuration
config = ConfigManager()

def setup_database():
    # Function scope - modifying global config
    config.set_config("db_host", "localhost")
    config.set_config("db_port", 5432)
    
    def set_credentials():
        # Nested function scope - still modifying global config
        config.set_config("db_user", "admin")
        config.set_config("db_password", "secret")
    
    set_credentials()

def setup_api():
    config.set_config("api_key", "abc123")
    config.set_config("api_url", "https://api.example.com")

# Setup configuration
setup_database()
setup_api()

print("Final configuration:")
config.show_all_config()

Example 2: Closure Example

def create_multiplier(factor):
    """Creates a function that multiplies by a given factor"""
    
    def multiplier(number):
        # This function "closes over" the factor variable
        return number * factor
    
    return multiplier

def create_accumulator(initial_value=0):
    """Creates a function that accumulates values"""
    total = initial_value
    
    def accumulate(value):
        nonlocal total
        total += value
        return total
    
    def get_total():
        return total
    
    def reset():
        nonlocal total
        total = initial_value
    
    # Return multiple functions that share the same scope
    accumulate.get_total = get_total
    accumulate.reset = reset
    return accumulate

# Usage
double = create_multiplier(2)
triple = create_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

# Accumulator example
acc = create_accumulator(10)
print(acc(5))           # 15
print(acc(3))           # 18
print(acc.get_total())  # 18
acc.reset()
print(acc.get_total())  # 10

Common Scope Pitfalls

Pitfall 1: Late Binding Closures

# Problem: All functions refer to the same variable
functions = []
for i in range(3):
    functions.append(lambda: i)  # All lambdas refer to the same 'i'

# All functions return 2 (the final value of i)
for func in functions:
    print(func())  # 2, 2, 2

# Solution 1: Use default parameter
functions_fixed1 = []
for i in range(3):
    functions_fixed1.append(lambda x=i: x)

for func in functions_fixed1:
    print(func())  # 0, 1, 2

# Solution 2: Use closure
def make_func(n):
    return lambda: n

functions_fixed2 = []
for i in range(3):
    functions_fixed2.append(make_func(i))

for func in functions_fixed2:
    print(func())  # 0, 1, 2

Pitfall 2: Mutable Default Arguments

# Problem: Mutable default argument
def add_item(item, target_list=[]):  # Don't do this!
    target_list.append(item)
    return target_list

# The same list is reused across calls
list1 = add_item("first")
list2 = add_item("second")
print(list1)  # ['first', 'second'] - unexpected!
print(list2)  # ['first', 'second'] - same list!

# Solution: Use None as default
def add_item_fixed(item, target_list=None):
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list

list3 = add_item_fixed("first")
list4 = add_item_fixed("second")
print(list3)  # ['first']
print(list4)  # ['second']

Best Practices

  • Use descriptive variable names to avoid confusion
  • Minimize use of global variables
  • Use global and nonlocal keywords explicitly when needed
  • Be careful with mutable default arguments
  • Understand closure behavior with loops
  • Keep functions small and focused to reduce scope complexity
  • Use class methods and static methods appropriately

The LEGB Rule

Python resolves a name by searching four scopes in order: Local → Enclosing → Global → Built-in. The first match wins.

ScopeWhere
LocalInside the current function
EnclosingIn an outer (nested) function
GlobalAt the top level of the module
Built-inprint, len, … always available

global and nonlocal

count = 0

def increment():
    global count       # rebind the module-level variable
    count += 1

increment()
print(count)           # 1

def outer():
    x = "outer"
    def inner():
        nonlocal x     # rebind the enclosing variable
        x = "changed"
    inner()
    return x

print(outer())         # changed

Reassigning a global inside a function without declaring global creates a new local variable instead — a common source of bugs. Prefer returning values over mutating globals.

Try It Yourself

Exercise 1: Predict the output.

x = 10
def f():
    x = 20
    print(x)
f()
print(x)
Show solution

Prints 20 then 10. Inside f, x = 20 creates a new local variable; the global x is untouched.

Exercise 2: Fix a counter function so it increments a module-level total.

Show solution
total = 0
def add():
    global total
    total += 1
add(); add()
print(total)   # 2

Key Takeaways

  • Names resolve by LEGB: Local, Enclosing, Global, Built-in.
  • Use global/nonlocal to rebind outer variables (sparingly).
  • Assigning inside a function makes a local unless declared otherwise.

📘 Real-World Deep Dive

Python's variable-resolution chain is "LEGB" (Local, Enclosing, Global, Builtins) and the <code>global</code>/<code>nonlocal</code> statements gate mutation. Misunderstanding this is the source of countless silent bugs.

Real-Life Scenario

A counter-as-closure used as a memoisation decorator, and a logging counter threaded through an inline function with <code>nonlocal</code>.

Real-Life Example

from typing import Any

# 1) Simple counter closure with 'nonlocal' for mutation.
def make_counter():
    n = 0
    def incr() -> int:
        nonlocal n            # rebind outer n
        n += 1
        return n
    return incr

ticks = make_counter()
print("tick:", ticks(), ticks(), ticks())

# 2) A safety wrapper that records how many times a function runs.
def count_calls(fn):
    n = 0
    def wrapper(*args, **kw):
        nonlocal n
        n += 1
        print(f"{fn.__name__} call #{n}")
        return fn(*args, **kw)
    return wrapper

@count_calls
def greet(name: str) -> str:
    return f"hello, {name}"

for who in ["Ada", "Bo", "Cy"]:
    greet(who)

Expected Output

tick: 1 2 3
greet call #1
greet call #2
greet call #3

Common mistakes

  • Globals are not shared between threads implicitly; rely on threading.local instead.
  • Comprehensions create their own scope; a variable defined inside doesn't leak out.
  • nonlocal requires a real enclosing scope — at module level, nonlocal is a SyntaxError.

🚀 Performance & Best Practices

  • Local lookups are faster than global — bind hot globals to a local inside the function.
  • Closures over many free variables slow attribute access measurably; keep captures small.
  • Use classes instead of closures for stateful objects with multiple fields.

🧪 Try It Yourself

  1. Replace count_calls with a class decorator that holds count + args.
  2. Add a reset_count helper that uses nonlocal to zero the counter.
  3. Write a cached_property example using nonlocal for the cache.

FAQ: Python Scope

Common questions about this page.

What is Python Scope?

Python Scope is a Python Tutorial lesson that explains python scope in Python. Learn about variable scope in Python and how Python resolves variable names using the LEGB rule. Copy the samples and run them in the Python editor. It is written for beginners who want a clear definition and working examples.

Should I run python scope examples locally for better learning?

Yes. Use the browser editor on StudyGrid for a quick check, then Download the example and run it on your computer. Local runs show real errors and the real toolchain, which is one of the fastest ways to learn python scope in this Python Tutorial Python lesson (Python Scope).

How do I use python scope in Python?

To use python scope in Python, follow the examples on this StudyGrid page. Copy a snippet, run it in the browser, then Download and run it locally for better learning. Change the values and compare the output.

What is the syntax of python scope?

This Python Scope tutorial shows python scope syntax with short Python examples. Use the code blocks in this lesson for the exact statements, then try them in your editor.

Python Scope example for beginners

Yes. This page includes a beginner python scope example you can copy and run. It is designed for searches such as "python scope for beginners", "python scope example", and "how to use python scope".

What are common mistakes with python scope?

Common python scope mistakes include wrong syntax, mixing types, and skipping practice. Work through this Python Tutorial chapter in order, run every example, and check the output before moving on.

Why should I learn python scope?

Python Scope is used in real Python work. Learning python scope helps you write clearer programs and continue the Python Tutorial tutorial on StudyGrid.

Is Python Scope free to learn online?

Yes. You can learn python scope free on StudyGrid (studygrid.in). This chapter is part of the Python Tutorial path and includes examples, syntax, and next-step links.