Python Scope
When you're working with variables in Python, one crucial concept to understand is "scope." Scope defines where in your program a variable can be accessed or modified. Mastering Python's scope rules will help you write better, bug-free code and understand how information flows through your programs.
What is Scope?
In Python, scope refers to the region of your code where a variable is visible and accessible. When you create a variable, it exists within a specific scope, which determines where you can use that variable.
Think of scope as the "visibility" of variables - some variables can be seen and accessed from anywhere in your code, while others are only visible in specific sections.
Python's LEGB Rule
Python follows the LEGB rule to determine the scope of variables. LEGB stands for:
- Local
- Enclosing
- Global
- Built-in
Let's explore each of these scopes in detail.
Local Scope
A variable created inside a function belongs to the local scope of that function and can only be used inside that function.
def my_function():
# Local variable
message = "Hello, local scope!"
print(message)
my_function() # Output: Hello, local scope!
# This would cause an error because message is not defined outside the function
# print(message) # NameError: name 'message' is not defined
Enclosing (or Nonlocal) Scope
This refers to variables in the outer function when you have nested functions.
def outer_function():
outer_var = "I'm in the outer function"
def inner_function():
print(outer_var) # Accessing variable from the enclosing scope
inner_function()
outer_function() # Output: I'm in the outer function
The inner function can access variables from the outer function, but not modify them directly (unless declared as nonlocal
).
def outer_function():
count = 0
def inner_function():
nonlocal count # This allows modification of the enclosing scope variable
count += 1
print(f"Count inside inner function: {count}")
inner_function()
print(f"Count after calling inner function: {count}")
outer_function()
# Output:
# Count inside inner function: 1
# Count after calling inner function: 1
Global Scope
Variables created outside of any function have global scope, meaning they can be accessed from any part of the code.
# Global variable
global_var = "I'm a global variable"
def show_global():
print(global_var)
show_global() # Output: I'm a global variable
print(global_var) # Output: I'm a global variable
However, if you want to modify a global variable inside a function, you need to use the global
keyword:
counter = 0
def increment_counter():
global counter # Declare that we want to modify the global variable
counter += 1
print(f"Counter inside function: {counter}")
print(f"Counter before: {counter}") # Output: Counter before: 0
increment_counter() # Output: Counter inside function: 1
print(f"Counter after: {counter}") # Output: Counter after: 1
Built-in Scope
Python comes with many built-in functions like print()
, len()
, and range()
. These are always available in your code without needing to import them.
# Built-in functions are available everywhere
length = len("Python")
print(length) # Output: 6
Scope Resolution: How Python Looks for Variables
When you reference a variable in your code, Python follows the LEGB rule to find it:
- It first looks in the Local scope (the current function)
- Then it checks the Enclosing scope (any containing functions)
- Next, it searches the Global scope (module level)
- Finally, it checks the Built-in scope
Once it finds a matching variable name, it stops searching. If no match is found, Python raises a NameError
.
Practical Examples
Example 1: Name Shadowing
When variables in different scopes have the same name, the local variable "shadows" the global one:
x = "global"
def demo_shadowing():
x = "local"
print(f"Inside function: x = {x}") # Uses the local x
demo_shadowing() # Output: Inside function: x = local
print(f"Outside function: x = {x}") # Uses the global x, Output: Outside function: x = global
Example 2: Creating a Counter Function
def create_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = create_counter()
print(counter()) # Output: 1
print(counter()) # Output: 2
print(counter()) # Output: 3
# Create another independent counter
counter2 = create_counter()
print(counter2()) # Output: 1
print(counter()) # Output: 4 (the first counter continues from where it left off)
This is a practical example of a closure, where the inner function remembers the environment in which it was created.
Example 3: Module-Level Scope
In a real application, you'll often work with multiple files. Each Python file (module) has its own global scope.
Imagine you have two files:
config.py:
# Global to this module
app_name = "My Application"
version = "1.0.0"
def get_app_info():
return f"{app_name} v{version}"
main.py:
import config
# Access variables from the imported module
print(config.app_name) # Output: My Application
print(config.get_app_info()) # Output: My Application v1.0.0
# This creates a variable in the global scope of main.py
app_name = "Different App"
print(app_name) # Output: Different App
# The original is unchanged
print(config.app_name) # Output: My Application
Common Pitfalls and Best Practices
Pitfall: Forgetting to Use global
or nonlocal
count = 0
def increment_bad():
# This creates a new local variable instead of modifying the global one
count = count + 1 # UnboundLocalError: local variable 'count' referenced before assignment
# The correct way:
def increment_good():
global count
count = count + 1
Best Practice: Minimize Global Variables
Global variables can make code harder to understand and debug. It's generally better to pass variables as arguments:
# Not recommended
total = 0
def add_to_total(value):
global total
total += value
# Better approach
def add(a, b):
return a + b
result = 0
result = add(result, 5)
result = add(result, 10)
Best Practice: Use Function Parameters and Return Values
# Less clear, using global
message = ""
def set_greeting(name):
global message
message = f"Hello, {name}!"
set_greeting("Alice")
print(message) # Output: Hello, Alice!
# More clear, using parameters and return values
def create_greeting(name):
return f"Hello, {name}!"
greeting = create_greeting("Bob")
print(greeting) # Output: Hello, Bob!
Summary
Python's scope rules determine where variables are accessible in your code:
- Local scope: Variables defined within a function
- Enclosing scope: Variables from outer functions
- Global scope: Variables defined at the module level
- Built-in scope: Python's pre-defined functions and variables
Understanding scope helps you:
- Prevent variable naming conflicts
- Control which parts of your program can access data
- Write more maintainable code by limiting variable visibility
Remember the LEGB rule for how Python searches for variables, and use the global
and nonlocal
keywords when you need to modify variables from outer scopes.
Exercises
- Write a function that increments a global counter and returns its new value.
- Create a nested function that can access and modify a variable from its parent function.
- Write a program with a local variable that shadows a global variable, and demonstrate the difference.
- Create a function that generates a series of functions, each remembering a different value.
- Rewrite a program that uses global variables to instead use function parameters and return values.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)