- Understand local and global scope
- Know how variable visibility works in functions
- Use the global keyword appropriately
- Avoid common scope-related bugs
Scope and Variable Lifetime
Have you ever wondered why a variable you created inside a function disappears after the function ends? Or why you can't change a variable from outside a function without special steps? Welcome to the world of scope.
Scope determines where a variable can be seen and used. Understanding scope prevents confusing bugs and helps you write cleaner, more predictable code. It's like understanding that a library card only works at your local library – context matters!
Local vs Global Scope
Think of scope like rooms in a house:
Scope Visualization
GLOBAL SCOPE (The House)
name = "Alice" ← Available everywhere
count = 0 ← Available everywhere
LOCAL SCOPE (A Room - function)
def greet():
message = "Hello" ← Only in this room
temp = 42 ← Only in this room
print(name) ← Can see house stuff
# Can't see message or temp out here!
Local Variables
Variables created inside a function are local – they only exist within that function:
def my_function():
local_var = "I only exist here!" # Local variable
print(local_var) # Works
my_function() # Prints: I only exist here!
# Outside the function:
# print(local_var) # NameError: name 'local_var' is not defined
Local Variables Are Isolated
def function_a():
secret = "A's secret"
print(f"A says: {secret}")
def function_b():
secret = "B's secret" # Different variable, same name!
print(f"B says: {secret}")
function_a() # A says: A's secret
function_b() # B says: B's secret
# Each function has its own 'secret' - they don't interfere!
Local Variables Reset Each Call
def counter():
count = 0 # Created fresh each call
count += 1
return count
print(counter()) # 1
print(counter()) # 1 (not 2! - count is recreated each time)
print(counter()) # 1
Global Variables
Variables created outside any function are global – accessible everywhere:
# Global variable - defined at the top level
greeting = "Hello"
def say_greeting():
# Can READ global variables without any special keyword
print(greeting) # Works - reading global
say_greeting() # Hello
print(greeting) # Hello - also works here
Reading Global Variables
PI = 3.14159 # Global constant
TAX_RATE = 0.2 # Global constant
def calculate_circle_area(radius):
return PI * radius ** 2 # Reading PI - works fine!
def calculate_tax(amount):
return amount * TAX_RATE # Reading TAX_RATE - works fine!
print(calculate_circle_area(5)) # 78.53975
print(calculate_tax(100)) # 20.0
The Shadowing Problem
What happens when a local variable has the same name as a global?
name = "Alice" # Global
def greet():
name = "Bob" # Local - SHADOWS the global!
print(f"Hello, {name}") # Uses local 'name'
greet() # Hello, Bob
print(name) # Alice - global unchanged!
Visual Explanation
Variable Shadowing
name = "Alice" ← GLOBAL name
def greet():
name = "Bob" ← LOCAL name (shadows global)
print(name) ← Uses LOCAL name = "Bob"
greet() → prints "Bob"
print(name) → prints "Alice" (global unchanged)
Python looks for 'name' in this order:
1. Local scope (inside function) ← Found "Bob"
2. Global scope (if not found locally)
The global Keyword
To modify a global variable inside a function, use global:
counter = 0 # Global
def increment():
global counter # Tell Python: use the GLOBAL counter
counter += 1 # Now this modifies the global!
print(counter) # 0
increment()
print(counter) # 1
increment()
print(counter) # 2
Without global - Creates Local Copy
counter = 0 # Global
def broken_increment():
# counter += 1 # UnboundLocalError!
# Python sees assignment, assumes local, but local doesn't exist yet
pass
def also_broken():
counter = counter + 1 # Same error!
# Python prepares a local 'counter', but tries to read it before creation
When global Is Needed
When to Use global
READING global - NO global keyword needed
total = 100
def show():
print(total) # Just reading - OK!
MODIFYING global - global keyword REQUIRED
total = 100
def add(amount):
global total # Must declare!
total += amount # Now modification works
⏱ Variable Lifetime
How long does a variable exist?
Variable Lifetime
GLOBAL VARIABLES:
• Created when the script starts
• Live until the program ends
• Always available
LOCAL VARIABLES:
• Created when the function is called
• Destroyed when the function returns
• Fresh copy each function call
Timeline:
Program starts → global vars created
↓
Function called → local vars created
↓
Function returns → local vars destroyed
↓
Program ends → global vars destroyed
Demonstration
print("1. Program starts")
message = "Global message" # Created now, lives until program ends
print(f"2. Global created: {message}")
def create_local():
print("4. Function called - local about to be created")
local_msg = "Local message" # Created now
print(f"5. Local exists: {local_msg}")
return "done"
# local_msg is destroyed HERE
print("3. About to call function")
create_local()
print("6. Function returned - local is destroyed")
# print(local_msg) # Would fail - doesn't exist anymore!
print("7. Program ends - global destroyed")
Best Practices
1. Minimize Global Variables
# BAD: Lots of globals
total = 0
tax = 0
discount = 0
def calculate():
global total, tax, discount
# ... hard to track what's changing
# GOOD: Use parameters and return values
def calculate(subtotal, tax_rate, discount_rate):
tax = subtotal * tax_rate
discount = subtotal * discount_rate
total = subtotal + tax - discount
return total, tax, discount
2. Use Constants for Unchanging Globals
# GOOD: Global constants (uppercase convention)
PI = 3.14159
MAX_ATTEMPTS = 3
DATABASE_URL = "localhost:5432"
def calculate_circle(radius):
return PI * radius ** 2 # Reading constant is fine!
3. Pass Data Through Parameters
# BAD: Relies on global state
user_name = ""
def greet():
global user_name
print(f"Hello, {user_name}")
user_name = "Alice"
greet()
# GOOD: Explicit parameter passing
def greet(name):
print(f"Hello, {name}")
greet("Alice") # Clear what data the function uses!
Practical Examples
Example 1: Game Score (Using Global Carefully)
# Sometimes global state makes sense (like a game score)
score = 0
high_score = 0
def add_points(points):
global score, high_score
score += points
if score > high_score:
high_score = score
print(f"Score: {score} (High: {high_score})")
def reset_game():
global score
score = 0
print("Game reset!")
add_points(100) # Score: 100 (High: 100)
add_points(50) # Score: 150 (High: 150)
reset_game() # Game reset!
add_points(75) # Score: 75 (High: 150)
Example 2: Configuration (Constants)
# Global configuration - read-only constants
CONFIG = {
"debug": True,
"max_retries": 3,
"timeout": 30
}
def make_request(url):
for attempt in range(CONFIG["max_retries"]):
if CONFIG["debug"]:
print(f"Attempt {attempt + 1} to {url}")
# ... make request
return "Success"
return "Failed"
result = make_request("https://api.example.com")
Example 3: Better Alternative - Class
# For complex state, consider a class instead of globals
class GameState:
def __init__(self):
self.score = 0
self.high_score = 0
self.level = 1
def add_points(self, points):
self.score += points
if self.score > self.high_score:
self.high_score = self.score
def reset(self):
self.score = 0
self.level = 1
# Use instance instead of globals
game = GameState()
game.add_points(100)
print(game.score) # 100
Common Mistakes
1. UnboundLocalError
count = 10
def increment():
count += 1 # UnboundLocalError!
# Python sees assignment, prepares local, but can't read before assignment
# Fix with global:
def increment_fixed():
global count
count += 1 # Works
2. Accidentally Shadowing
data = [1, 2, 3] # Global list
def process():
data = [4, 5, 6] # Created local, didn't modify global!
data.append(7)
process()
print(data) # [1, 2, 3] - unchanged!
# If you wanted to modify:
def process_fixed():
global data
data = [4, 5, 6] # Now modifies global
3. Mutable Default Arguments
# DANGER: Mutable default argument!
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ← Shared list!
print(add_item("c")) # ['a', 'b', 'c'] ← Still shared!
# SAFE: Use None as default
def add_item_safe(item, items=None):
if items is None:
items = [] # New list each call
items.append(item)
return items
Key Takeaways
Remember These Points
Local variables: Inside function, exist only during call
Global variables: Outside functions, exist throughout
Functions can READ global variables freely
To MODIFY global, use: global variable_name
Same name in function = local variable (shadows global)
Best practice: Use parameters and return values
instead of relying on globals
Use UPPERCASE for global constants
Module Complete!
Congratulations! You've completed the Functions module!
You now understand:
- Defining functions with
def - Parameters and arguments for input
- Return values for output
- Scope for variable visibility
Functions are the building blocks of organized code. In the next module, you'll learn about Strings and Text Processing – powerful techniques for working with text data!
