Skip to Content
Course content

72: Scope: Local, Enclosing, Global, Built-in

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most frustrating things you'll encounter in Python is the UnboundLocalError. You'll look at your code, see a variable defined right there at the top of your script, and wonder why Python is insisting it doesn't exist. This happens because of "Scope"—the set of rules Python uses to decide which variable you're actually talking about when you use a name.

Python follows the LEGB rule: it looks for a name in the Local scope first, then Enclosing, then Global, and finally Built-in. Let's build a simple Quest Reward system to see this in action.

Setting the Global Stage

First, let's establish a global variable. This is something that should be accessible from anywhere in our script, like the player's total experience points.

total_xp = 0

def add_xp(amount):
    print(f"Adding {amount} XP...")
    # We are just reading total_xp here, which is fine.
    print(f"Current Total: {total_xp}")

add_xp(10)

In this snippet, Python looks for total_xp inside add_xp (Local). It's not there. It then looks at the top level of the script (Global) and finds it. This is a read-only operation, and it works perfectly.

Creating a Quest Wrapper for Enclosing Scope

Now, things get interesting. Sometimes we want a function that "configures" another function. This is where Enclosing scope comes in. I'll create a function that sets a multiplier for a specific quest, and inside that, a function that calculates the final reward.

def start_quest(multiplier):
    # This is the Enclosing scope for the inner function
    quest_name = "Dragon's Lair"

    def calculate_reward(base_xp):
        # Local scope: bonus is only known here
        bonus = 50 
        # Python finds quest_name and multiplier in the Enclosing scope
        return (base_xp * multiplier) + bonus

    return calculate_reward

# We create a specific quest function with a 2x multiplier
fire_quest = start_quest(2)
print(fire_quest(100)) # (100 * 2) + 50 = 250

When calculate_reward runs, Python looks for bonus locally. Then it looks "outward" to the start_quest function to find multiplier and quest_name. That middle layer is the Enclosing scope.

The 'UnboundLocalError' Trap

Here is where I usually trip up when I'm rushing a project. I'll try to update the total_xp from inside a function. I'm going to write this exactly how I did it the first time I learned this, and watch it fail.

total_xp = 0

def complete_quest():
    # I want to add 100 to the global total_xp
    total_xp = total_xp + 100 
    print("Quest complete!")

complete_quest()

If you run this, Python will throw an UnboundLocalError: local variable 'total_xp' referenced before assignment. Why? Because the moment Python sees total_xp = ... inside a function, it decides that total_xp MUST be a local variable. But then it tries to read total_xp (on the right side of the equals sign) before it has been defined locally. It doesn't even bother checking the Global scope once it thinks the variable is local.

Fixing the Scope Conflict

To fix this, we have to explicitly tell Python: "Don't create a new local variable; use the one from the global scope." We use the global keyword for this. If we were dealing with the enclosing scope (the middle layer), we'd use nonlocal.

total_xp = 0

def complete_quest():
    global total_xp # This tells Python to look at the Global scope
    total_xp += 100
    print(f"Quest complete! Total XP is now {total_xp}")

complete_quest()

Finally, there's the Built-in scope. These are names like len(), max(), or print(). Python checks these last. If you accidentally named a variable len = 10 in your global scope, you'd "shadow" the built-in function, and trying to use len([1, 2]) would crash because Python found your integer first. Avoid doing that—it's a recipe for a very confusing afternoon.




📋 Practical Task

Refactoring the Game Inventory Scope

You are working on a game where a player has a global inventory_capacity and a local current_item_count. The following code is broken because it tries to modify a global variable without the proper keyword, and it also accidentally shadows a built-in function.

inventory_capacity = 10
current_item_count = 0

def add_item(item_name):
    # BUG 1: This will cause an UnboundLocalError
    inventory_capacity = inventory_capacity - 1 
    
    # BUG 2: 'sum' is a built-in function. 
    # This variable name shadows it, which is bad practice.
    sum = current_item_count + 1 
    
    print(f"Added {item_name}. Space remaining: {inventory_capacity}")

add_item("Magic Sword")

Your Task: Rewrite the add_item function so that it correctly updates the global inventory_capacity and renames the sum variable to something more appropriate (like new_count) to avoid shadowing the built-in sum() function.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.