Scope and Namespaces
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand the concept of scope in Python
- - Distinguish between local and global scope
- - Use the `global` keyword correctly
- - Understand the `nonlocal` keyword
- - Understand Python's namespace concept
- - Recognize scope resolution order (LEGB rule)
- - Apply scope knowledge to write correct code
- - Avoid common scope-related errors
- - Understand how scope affects variable access
Lesson 6.3: Scope and Namespaces
Learning Objectives
By the end of this lesson, you will be able to:
- Understand the concept of scope in Python
- Distinguish between local and global scope
- Use the
globalkeyword correctly - Understand the
nonlocalkeyword - Understand Python's namespace concept
- Recognize scope resolution order (LEGB rule)
- Apply scope knowledge to write correct code
- Avoid common scope-related errors
- Understand how scope affects variable access
Introduction to Scope
Scope determines where in your code a variable can be accessed. Understanding scope is crucial for writing correct Python programs and avoiding bugs related to variable access.
What is Scope?
Scope defines the visibility and lifetime of variables:
- Visibility: Where a variable can be accessed
- Lifetime: How long a variable exists
Python has different levels of scope, and variables are accessible based on where they're defined.
Types of Scope
Python has four main levels of scope (from narrowest to broadest):
- Local (L) - Inside the current function
- Enclosing (E) - In enclosing functions (for nested functions)
- Global (G) - At the module level
- Built-in (B) - Built-in Python names
This is known as the LEGB rule (Local, Enclosing, Global, Built-in).
Local Scope
Local scope refers to variables defined inside a function. These variables are only accessible within that function.
Basic Local Scope
def my_function():
local_var = "I'm local"
print(local_var)
my_function() # Output: I'm local
# print(local_var) # NameError: name 'local_var' is not defined
Local Variables Are Independent
def function1():
x = 10
print(f"function1: x = {x}")
def function2():
x = 20 # Different variable, same name
print(f"function2: x = {x}")
function1() # Output: function1: x = 10
function2() # Output: function2: x = 20
# Each function has its own 'x'
Parameters Are Local
def greet(name): # 'name' is a local variable
print(f"Hello, {name}")
greet("Alice") # Output: Hello, Alice
# print(name) # NameError: name 'name' is not defined
Local Scope Examples
def calculate_area(length, width):
# 'length', 'width', and 'area' are all local
area = length * width
return area
result = calculate_area(5, 3)
print(result) # Output: 15
# print(area) # NameError: 'area' is not defined
Global Scope
Global scope refers to variables defined at the module level (outside any function). These variables are accessible throughout the module.
Basic Global Scope
# Global variable
global_var = "I'm global"
def my_function():
# Can access global variable
print(global_var)
my_function() # Output: I'm global
print(global_var) # Also accessible here
Reading Global Variables
You can read global variables without any special keyword:
global_count = 0
def show_count():
print(f"Count is: {global_count}") # Can read global
show_count() # Output: Count is: 0
Modifying Global Variables
To modify a global variable inside a function, use the global keyword:
count = 0
def increment():
global count # Declare we're using global variable
count += 1
print(f"Count: {count}")
increment() # Output: Count: 1
increment() # Output: Count: 2
print(count) # Output: 2
Without global Keyword
count = 0
def try_increment():
# Without 'global', Python thinks this is a new local variable
count = count + 1 # UnboundLocalError!
# try_increment() # UnboundLocalError: local variable 'count' referenced before assignment
Global Scope Examples
# Global variables
PI = 3.14159
APP_NAME = "My Application"
def calculate_circle_area(radius):
# Can read global PI
return PI * radius ** 2
def get_app_info():
# Can read global APP_NAME
return f"Welcome to {APP_NAME}"
area = calculate_circle_area(5)
print(area) # Output: 78.53975
info = get_app_info()
print(info) # Output: Welcome to My Application
The global Keyword
The global keyword declares that you want to use a global variable inside a function.
Basic Usage
x = 10 # Global variable
def modify_global():
global x # Declare we're modifying global x
x = 20 # Modify global variable
print(f"Inside function: x = {x}")
print(f"Before: x = {x}") # Output: Before: x = 10
modify_global() # Output: Inside function: x = 20
print(f"After: x = {x}") # Output: After: x = 20
Creating Global Variables
You can also create global variables from inside a function:
def create_global():
global new_global
new_global = "I was created in a function"
print(new_global)
create_global()
print(new_global) # Output: I was created in a function
Multiple Global Variables
x = 1
y = 2
z = 3
def modify_globals():
global x, y, z # Declare multiple globals
x = 10
y = 20
z = 30
print(f"Before: x={x}, y={y}, z={z}") # Output: Before: x=1, y=2, z=3
modify_globals()
print(f"After: x={x}, y={y}, z={z}") # Output: After: x=10, y=20, z=30
When to Use global
Use global when:
- You need to modify a global variable
- You want to create a global variable from inside a function
Avoid global when:
- You only need to read the global variable (not required)
- You can pass values as parameters and return results instead
Best Practice: Prefer passing parameters and returning values over using global variables.
# Less ideal: using global
counter = 0
def increment_global():
global counter
counter += 1
# Better: pass and return
def increment(value):
return value + 1
counter = 0
counter = increment(counter)
The nonlocal Keyword
The nonlocal keyword is used in nested functions to access variables from the enclosing (non-global) scope.
Basic nonlocal Usage
def outer_function():
x = "outer" # Enclosing scope variable
def inner_function():
nonlocal x # Access enclosing scope variable
x = "inner" # Modify enclosing scope variable
print(f"Inside inner: x = {x}")
print(f"Before inner: x = {x}") # Output: Before inner: x = outer
inner_function() # Output: Inside inner: x = inner
print(f"After inner: x = {x}") # Output: After inner: x = inner
outer_function()
nonlocal vs global
# Global variable
global_var = "global"
def outer():
# Enclosing variable
enclosing_var = "enclosing"
def inner():
# Can access global (read only, or use 'global' to modify)
print(f"Global: {global_var}")
# Use 'nonlocal' to modify enclosing variable
nonlocal enclosing_var
enclosing_var = "modified"
print(f"Enclosing: {enclosing_var}")
print(f"Before inner: {enclosing_var}") # Output: Before inner: enclosing
inner() # Output: Global: global
# Enclosing: modified
print(f"After inner: {enclosing_var}") # Output: After inner: modified
outer()
Practical nonlocal Example
def counter():
count = 0 # Enclosing scope
def increment():
nonlocal count
count += 1
return count
return increment
# Create counter function
my_counter = counter()
print(my_counter()) # Output: 1
print(my_counter()) # Output: 2
print(my_counter()) # Output: 3
# Each counter is independent
another_counter = counter()
print(another_counter()) # Output: 1 (new counter)
Scope Resolution: LEGB Rule
Python follows the LEGB rule when looking up variable names:
- Local (L) - Look in local scope first
- Enclosing (E) - Look in enclosing functions
- Global (G) - Look in global scope
- Built-in (B) - Look in built-in names
LEGB Examples
# Built-in
print("Hello") # 'print' is built-in
# Global
x = "global"
def outer():
# Enclosing
x = "enclosing"
def inner():
# Local
x = "local"
print(x) # Output: local (found in local scope)
inner()
print(x) # Output: enclosing (found in enclosing scope)
outer()
print(x) # Output: global (found in global scope)
Scope Lookup Order
def example():
# Python looks for 'len' in this order:
# 1. Local scope (not found)
# 2. Enclosing scope (not found)
# 3. Global scope (not found)
# 4. Built-in scope (found!)
result = len([1, 2, 3]) # Uses built-in len()
return result
print(example()) # Output: 3
Shadowing Variables
When a local variable has the same name as a global variable, it shadows (hides) the global:
x = "global"
def my_function():
x = "local" # Shadows global x
print(x) # Output: local
my_function()
print(x) # Output: global (global x unchanged)
Nested Functions and Scope
Nested functions can access variables from enclosing functions.
Basic Nested Function Scope
def outer():
outer_var = "outer"
def inner():
# Can access outer_var (read)
print(f"Accessing: {outer_var}")
inner()
outer() # Output: Accessing: outer
Modifying Enclosing Variables
def outer():
count = 0
def inner():
nonlocal count # Required to modify
count += 1
print(f"Count: {count}")
inner() # Output: Count: 1
inner() # Output: Count: 2
print(f"Final count: {count}") # Output: Final count: 2
outer()
Multiple Levels of Nesting
def level1():
var1 = "level1"
def level2():
var2 = "level2"
def level3():
# Can access var1 and var2
nonlocal var1, var2
var1 = "modified1"
var2 = "modified2"
print(f"Level3: {var1}, {var2}")
level3()
print(f"Level2: {var1}, {var2}")
level2()
print(f"Level1: {var1}")
level1()
Common Scope Patterns
Pattern 1: Counter with Closure
def create_counter():
count = 0 # Enclosing scope
def counter():
nonlocal count
count += 1
return count
return counter
# Create independent counters
counter1 = create_counter()
counter2 = create_counter()
print(counter1()) # Output: 1
print(counter1()) # Output: 2
print(counter2()) # Output: 1 (independent)
print(counter1()) # Output: 3
Pattern 2: Configuration Access
# Global configuration
CONFIG = {
"debug": True,
"timeout": 30
}
def get_config(key):
"""Access global config."""
return CONFIG.get(key)
def set_config(key, value):
"""Modify global config."""
global CONFIG
CONFIG[key] = value
print(get_config("debug")) # Output: True
set_config("timeout", 60)
print(get_config("timeout")) # Output: 60
Pattern 3: Accumulator Function
def create_accumulator(initial=0):
total = initial # Enclosing scope
def add(value):
nonlocal total
total += value
return total
return add
acc = create_accumulator(10)
print(acc(5)) # Output: 15
print(acc(3)) # Output: 18
print(acc(7)) # Output: 25
Common Mistakes and Pitfalls
1. Modifying Global Without global Keyword
count = 0
def increment():
# count = count + 1 # UnboundLocalError!
# Python thinks 'count' is local because of assignment
# Correct
def increment():
global count
count += 1
2. Confusing Local and Global Variables
x = 10 # Global
def my_function():
x = 20 # Local (shadows global)
print(x) # Output: 20
my_function()
print(x) # Output: 10 (global unchanged)
3. Forgetting nonlocal in Nested Functions
def outer():
x = 10
def inner():
# x = 20 # Creates new local x, doesn't modify outer x!
nonlocal x
x = 20 # Correct: modifies outer x
inner()
print(x) # Output: 20
outer()
4. Trying to Access Local Before Assignment
def my_function():
print(x) # NameError if x is assigned later in function
x = 10
# my_function() # UnboundLocalError
# Correct: assign before use, or use global
def my_function():
x = 10
print(x)
5. Mutable Objects and Scope
# Lists and dictionaries are mutable
my_list = [1, 2, 3] # Global
def modify_list():
# Can modify without 'global' (modifying object, not reassigning)
my_list.append(4)
print(my_list) # Output: [1, 2, 3, 4]
modify_list()
# But reassignment requires 'global'
def reassign_list():
global my_list
my_list = [5, 6, 7] # Reassignment requires global
reassign_list()
print(my_list) # Output: [5, 6, 7]
Best Practices
1. Minimize Global Variables
# Less ideal: many globals
counter = 0
total = 0
average = 0
def process():
global counter, total, average
# Complex logic...
# Better: encapsulate in class or pass parameters
def process(counter, total):
# Process with parameters
new_total = total + 1
new_counter = counter + 1
return new_counter, new_total
2. Use Parameters and Return Values
# Less ideal: global state
result = None
def calculate(x, y):
global result
result = x + y
# Better: return value
def calculate(x, y):
return x + y
result = calculate(5, 3)
3. Clear Variable Names
# Good: clear names help understand scope
def process_user_data(user_name, user_age):
local_result = user_name.upper()
return local_result
# Less clear
def process(a, b):
x = a.upper()
return x
4. Document Scope Intentions
# Good: document when using global
GLOBAL_COUNTER = 0
def increment_counter():
"""Increment the global counter.
Note: Modifies global variable GLOBAL_COUNTER.
"""
global GLOBAL_COUNTER
GLOBAL_COUNTER += 1
Practice Exercise
Exercise: Scope and Namespaces Practice
Objective: Create a Python program that demonstrates scope concepts, global/local variables, and namespace usage.
Instructions:
-
Create a file called
scope_practice.py -
Write a program that:
- Demonstrates local and global scope
- Uses the
globalkeyword - Uses the
nonlocalkeyword - Shows nested function scope
- Implements practical scope-based solutions
-
Your program should include:
- Local variable examples
- Global variable examples
- Nested function examples
- Closure examples
- Scope resolution examples
Example Solution:
"""
Scope and Namespaces Practice
This program demonstrates scope concepts and namespace usage.
"""
print("=" * 60)
print("SCOPE AND NAMESPACES PRACTICE")
print("=" * 60)
print()
# 1. Local Scope
print("1. LOCAL SCOPE")
print("-" * 60)
def my_function():
local_var = "I'm local"
print(f" Inside function: {local_var}")
my_function()
# print(local_var) # NameError if uncommented
print()
# 2. Global Scope
print("2. GLOBAL SCOPE")
print("-" * 60)
global_var = "I'm global"
def read_global():
# Can read global without 'global' keyword
print(f" Reading global: {global_var}")
read_global()
print(f" Outside function: {global_var}")
print()
# 3. Modifying Global Variables
print("3. MODIFYING GLOBAL VARIABLES")
print("-" * 60)
count = 0
def increment():
global count
count += 1
print(f" Count: {count}")
print(f" Before: count = {count}")
increment()
increment()
print(f" After: count = {count}")
print()
# 4. Local vs Global (Shadowing)
print("4. LOCAL vs GLOBAL (SHADOWING)")
print("-" * 60)
x = "global"
def my_function():
x = "local" # Shadows global x
print(f" Inside function: x = '{x}'")
my_function()
print(f" Outside function: x = '{x}'")
print()
# 5. Reading vs Modifying
print("5. READING vs MODIFYING")
print("-" * 60)
value = 10
def read_only():
# Can read without 'global'
print(f" Reading: {value}")
def modify():
global value
value = 20
print(f" Modified to: {value}")
read_only()
modify()
read_only()
print()
# 6. Nested Functions - Reading Enclosing
print("6. NESTED FUNCTIONS - READING ENCLOSING")
print("-" * 60)
def outer():
outer_var = "outer variable"
def inner():
# Can read enclosing variable
print(f" Inner accessing: {outer_var}")
inner()
outer()
print()
# 7. Nested Functions - Modifying Enclosing (nonlocal)
print("7. NESTED FUNCTIONS - MODIFYING ENCLOSING")
print("-" * 60)
def outer():
count = 0
def inner():
nonlocal count
count += 1
print(f" Inner count: {count}")
print(f" Before inner: count = {count}")
inner()
inner()
print(f" After inner: count = {count}")
outer()
print()
# 8. Closure Example
print("8. CLOSURE EXAMPLE")
print("-" * 60)
def create_counter():
count = 0 # Enclosing scope
def counter():
nonlocal count
count += 1
return count
return counter
counter1 = create_counter()
counter2 = create_counter()
print(f" Counter1: {counter1()}")
print(f" Counter1: {counter1()}")
print(f" Counter2: {counter2()}")
print(f" Counter1: {counter1()}")
print()
# 9. LEGB Rule Demonstration
print("9. LEGB RULE DEMONSTRATION")
print("-" * 60)
# Built-in
print(f" Built-in 'len': {len([1, 2, 3])}")
# Global
x = "global"
def outer():
# Enclosing
x = "enclosing"
def inner():
# Local
x = "local"
print(f" Local scope: x = '{x}'")
print(f" Enclosing scope: x = '{x}'")
inner()
print(f" Enclosing scope: x = '{x}'")
print(f" Global scope: x = '{x}'")
outer()
print(f" Global scope: x = '{x}'")
print()
# 10. Multiple Levels of Nesting
print("10. MULTIPLE LEVELS OF NESTING")
print("-" * 60)
def level1():
var1 = "level1"
def level2():
var2 = "level2"
def level3():
nonlocal var1, var2
var1 = "modified1"
var2 = "modified2"
print(f" Level3: var1='{var1}', var2='{var2}'")
print(f" Level2 before: var1='{var1}', var2='{var2}'")
level3()
print(f" Level2 after: var1='{var1}', var2='{var2}'")
print(f"Level1 before: var1='{var1}'")
level2()
print(f"Level1 after: var1='{var1}'")
level1()
print()
# 11. Accumulator Function
print("11. ACCUMULATOR FUNCTION")
print("-" * 60)
def create_accumulator(initial=0):
total = initial
def add(value):
nonlocal total
total += value
return total
return add
acc = create_accumulator(10)
print(f" Start: 10")
print(f" Add 5: {acc(5)}")
print(f" Add 3: {acc(3)}")
print(f" Add 7: {acc(7)}")
print()
# 12. Configuration Pattern
print("12. CONFIGURATION PATTERN")
print("-" * 60)
CONFIG = {
"debug": True,
"timeout": 30
}
def get_config(key):
return CONFIG.get(key)
def set_config(key, value):
global CONFIG
CONFIG[key] = value
print(f" Initial config: {CONFIG}")
print(f" Get 'debug': {get_config('debug')}")
set_config("timeout", 60)
print(f" After set_config: {CONFIG}")
print()
# 13. Mutable Objects and Scope
print("13. MUTABLE OBJECTS AND SCOPE")
print("-" * 60)
my_list = [1, 2, 3]
def modify_list():
# Can modify list without 'global' (modifying object)
my_list.append(4)
print(f" Modified list: {my_list}")
def reassign_list():
global my_list
my_list = [5, 6, 7] # Reassignment requires 'global'
print(f" Reassigned list: {my_list}")
print(f" Original: {my_list}")
modify_list()
print(f" After modify: {my_list}")
reassign_list()
print(f" After reassign: {my_list}")
print()
# 14. Independent Function Scopes
print("14. INDEPENDENT FUNCTION SCOPES")
print("-" * 60)
def function1():
x = 10
print(f" function1: x = {x}")
def function2():
x = 20 # Different variable, same name
print(f" function2: x = {x}")
function1()
function2()
print(" Each function has its own 'x'")
print()
# 15. Practical: Bank Account
print("15. PRACTICAL: BANK ACCOUNT")
print("-" * 60)
def create_account(initial_balance=0):
balance = initial_balance
def deposit(amount):
nonlocal balance
balance += amount
return balance
def withdraw(amount):
nonlocal balance
if amount <= balance:
balance -= amount
return balance
return "Insufficient funds"
def get_balance():
return balance
return {"deposit": deposit, "withdraw": withdraw, "balance": get_balance}
account = create_account(100)
print(f" Initial balance: {account['balance']()}")
print(f" After deposit 50: {account['deposit'](50)}")
print(f" After withdraw 30: {account['withdraw'](30)}")
print(f" Current balance: {account['balance']()}")
print()
print("=" * 60)
print("PRACTICE COMPLETE!")
print("=" * 60)
Expected Output (truncated):
============================================================
SCOPE AND NAMESPACES PRACTICE
============================================================
1. LOCAL SCOPE
------------------------------------------------------------
Inside function: I'm local
2. GLOBAL SCOPE
------------------------------------------------------------
Reading global: I'm global
Outside function: I'm global
[... rest of output ...]
Challenge (Optional):
- Create a more complex closure system
- Build a state management system
- Implement a decorator using closures
- Create a module-level configuration system
- Build a function factory pattern
Key Takeaways
- Scope determines variable visibility - where variables can be accessed
- LEGB rule - Local, Enclosing, Global, Built-in (lookup order)
- Local variables are only accessible within their function
- Global variables are accessible throughout the module
- Use
globalkeyword to modify global variables inside functions - Use
nonlocalkeyword to modify enclosing scope variables - Reading globals doesn't require
globalkeyword - Modifying requires declaration - use
globalornonlocal - Each function has its own scope - variables are independent
- Closures - nested functions can "remember" enclosing scope variables
Quiz: Scope and Namespaces
Test your understanding with these questions:
-
What is the LEGB rule?
- A) Local, Enclosing, Global, Built-in
- B) Local, Global, Enclosing, Built-in
- C) Built-in, Global, Enclosing, Local
- D) Global, Local, Enclosing, Built-in
-
Do you need
globalto read a global variable?- A) Yes, always
- B) No, only to modify
- C) Yes, for both read and write
- D) Never needed
-
What keyword is used to modify enclosing scope variables?
- A)
global - B)
nonlocal - C)
local - D)
enclosing
- A)
-
What happens if you assign to a variable without
global?- A) Creates local variable
- B) Modifies global
- C) Error
- D) Does nothing
-
Can nested functions access enclosing scope variables?
- A) No
- B) Yes, to read only
- C) Yes, to read and modify (with nonlocal)
- D) Only with global
-
What is a closure?
- A) A function that closes
- B) A nested function that remembers enclosing scope
- C) A global function
- D) A built-in function
-
What is variable shadowing?
- A) Hiding a variable
- B) Local variable hiding global with same name
- C) Global variable hiding local
- D) Error condition
-
What does
nonlocaldo?- A) Accesses global variables
- B) Accesses enclosing scope variables
- C) Creates new variables
- D) Deletes variables
-
Can you modify a list without
global?- A) No
- B) Yes, if modifying the object (not reassigning)
- C) Always requires global
- D) Only in Python 2
-
What is the scope of a parameter?
- A) Global
- B) Local
- C) Enclosing
- D) Built-in
Answers:
- A) Local, Enclosing, Global, Built-in (LEGB lookup order)
- B) No, only to modify (can read globals without global keyword)
- B)
nonlocal(for modifying enclosing scope variables) - A) Creates local variable (shadows global if same name)
- C) Yes, to read and modify (with nonlocal for modification)
- B) A nested function that remembers enclosing scope (closure)
- B) Local variable hiding global with same name
- B) Accesses enclosing scope variables (for modification)
- B) Yes, if modifying the object (not reassigning) - mutation vs reassignment
- B) Local (parameters are local to the function)
Next Steps
Excellent work! You've mastered scope and namespaces. You now understand:
- Local and global scope
- The
globalandnonlocalkeywords - LEGB rule for scope resolution
- Nested functions and closures
- Best practices for scope management
What's Next?
- Lesson 6.4: Lambda Functions
- Practice building functions with proper scope
- Learn about advanced scope patterns
- Explore decorators and closures
Additional Resources
- Python Scopes and Namespaces: docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces
- Naming and Binding: docs.python.org/3/reference/executionmodel.html#naming-and-binding
- Closures: Research Python closures and their applications
Lesson completed! You're ready to move on to the next lesson.
Course Navigation
- Function Basics
- Function Arguments
- Scope and Namespaces
- Lambda Functions
- Function Basics
- Function Arguments
- Scope and Namespaces
- Lambda Functions