Python Scope of Variables

In the last lesson, we learned about Python Lambda Functions. Now let’s understand where variables live and which parts of a program can use them.

Let’s Talk About Where Variables Live

Imagine we have a house. Inside the house, we have a kitchen. Inside the kitchen, we have a drawer. In that drawer, we keep a spoon.

Now, can someone in the living room use that spoon? Well, they would have to go to the kitchen, open the drawer, and take it. The spoon is not just lying around in the living room.

Variables in Python are like that. Each variable has a place where it lives. And where it lives determines who can use it.

That is what scope means. Scope is the area where a variable is accessible.

Why Does Scope Matter in Python?

Because if we do not understand scope, we will get confusing errors. We will try to use a variable outside the function where it was created. And Python will say, “I do not know what you are talking about.”

So let’s understand it properly.

The Four Levels of Scope in Python

Python has four levels of scope. They follow something called the LEGB rule.

  • L — Local
  • E — Enclosing
  • G — Global
  • B — Built-in

Let me explain each one.

Local Scope in Python

A variable created inside a function is local to that function. It lives inside the function. And it dies when the function finishes.

def my_function():
x = 10
print(x)
my_function()
print(x) # Error!

Output

10
NameError: name 'x' is not defined

See what happened? Inside the function, x exists. We can print it. But outside the function, x does not exist. Python has never heard of it.

That is local scope. The variable is local to the function. It is born when the function runs. And it disappears when the function ends.

Think of it like this. We write something on a whiteboard in a meeting room. When the meeting is over, someone erases the board. The information is gone. It only existed inside that room.

Global Scope in Python

A variable created outside any function is global. It lives in the main part of our program. And it can be accessed from anywhere.

x = 10
def my_function():
print(x)
my_function()
print(x)

Output

10
10

Here, x is created outside the function. So it is global. The function can read it. And the main program can read it.

But here is the catch. We can read a global variable inside a function. But we cannot change it. Not without permission.

x = 10
def my_function():
x = 20 # This creates a new local variable
print(x)
my_function()
print(x)

Output

20
10

Wait. Why did x stay 10 outside the function?

Because inside the function, x = 20 did not change the global x. It created a new local variable called x. They just happen to have the same name. But they are different variables.

Think of it like this. We have a friend named Alex. We also have a cousin named Alex. They are different people. Same name. Different people.

So inside the function, we created a new local x. The global x was not touched.

If we really want to change the global variable inside a function, we need to use the global keyword.

x = 10
def my_function():
global x
x = 20
my_function()
print(x)

Output

20

Now the global x is changed. Because we told Python: “I am talking about the global x, not a new local one.”

Let’s use global carefully. It can make our code confusing. Most of the time, we do not need it.

Enclosing Scope in Python

This one is about nested functions. A function inside another function.

def outer():
x = 10
def inner():
print(x)
inner()
outer()

Output

10

Here, x is in the outer function. The inner function can read it. Because it is in the enclosing scope.

But like before, if we try to change it, we need the nonlocal keyword.

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

Output

20

The nonlocal keyword tells Python: “I am talking about the x in the enclosing function, not a new local one.”

Again, let’s use it carefully.

Built-in Scope in Python

Python has some names built in. Like print, len, sum, max, min, and so on. These are always available. They are in the built-in scope.

print(len("hello"))

We do not need to define print or len. They are just there. That is built-in scope.

The LEGB Rule in Python

When we use a variable, Python looks for it in this order:

  1. Local — inside the current function
  2. Enclosing — inside any enclosing function
  3. Global — at the top level of the module
  4. Built-in — Python’s built-in names

It stops at the first place it finds the name. So if we have a local x and a global x, the local one wins inside the function.

Let me show us.

x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer()

Output

local

Because inner has its own local x. So that is what gets printed.

If we remove the local x:

x = "global"
def outer():
x = "enclosing"
def inner():
print(x)
inner()
outer()

Output

enclosing

Now Python looks in local scope. No x. Then enclosing scope. Finds x. So it prints “enclosing”.

If we remove the enclosing x:

x = "global"
def outer():
def inner():
print(x)
inner()
outer()

Output

global

Now Python looks in local. No. Enclosing. No. Global. Finds it. Prints “global”.

That is the LEGB rule. It always searches in that order.

Python Scope: A Real-Life Example

Let’s say we are building a simple game. We have a global variable for the score.

score = 0
def add_points(points):
global score
score += points
print(f"Score is now {score}")
add_points(10)
add_points(5)
print(f"Final score: {score}")

Output

Score is now 10
Score is now 15
Final score: 15

Here, we need global because we are changing the global score from inside the function.

Without global, score += points would create a local score. And the global would stay 0.

A Better Way

Actually, using global is often a sign of bad design. A better way is to return the new value and assign it outside.

score = 0
def add_points(current_score, points):
return current_score + points
score = add_points(score, 10)
score = add_points(score, 5)
print(f"Final score: {score}")

Output

Final score: 15

Same result. But no global. And the function is pure. It takes input. It gives output. It does not mess with anything outside.

That is cleaner. And easier to test.

So let’s try to avoid global. Use return values instead.

Common Mistakes with Python Scope

Mistake 1: Trying to Use a Local Variable Outside the Function

def my_function():
x = 10
my_function()
print(x) # Error!

x is local. It does not exist outside the function.

So what do we do? We use it inside the function, or return it.

Mistake 2: Thinking We Can Change a Global Without global

x = 10
def my_function():
x = 20 # This creates a local x, does not change global
my_function()
print(x) # 10

So what do we do? We use global if we really need to change it.

def my_function():
global x
x = 20

Mistake 3: Using global Too Much

If we find ourselves using global everywhere, our code is probably not well organized. Let’s try returning values instead.

Mistake 4: Forgetting nonlocal in Nested Functions

def outer():
x = 10
def inner():
x = 20 # Creates a new local x, does not change outer x
inner()
print(x) # 10

So what do we do? We use nonlocal.

def inner():
nonlocal x
x = 20

Next, we will learn about Recursion in Python and see how a function can call itself.