Master the concept of variable scope in Python functions and understand how variables are accessed and modified within different scopes.
Local scope refers to variables defined inside a function that are only accessible within that function.
Variables created inside a function are local to that function and cannot be accessed from outside. They are created when the function is called and destroyed when the function returns.
def calculate_square(number):
result = number ** 2 # Local variable
return result
# This works
print(calculate_square(5)) # Output: 25
# This would raise an error
# print(result) # NameError: name 'result' is not defined
Global scope refers to variables defined at the module level that are accessible throughout the entire program.
Global variables are accessible from any function in the module. However, to modify a global variable inside a function, you need to use the global keyword.
counter = 0 # Global variable
def increment_counter():
global counter # Declare we're using the global variable
counter += 1
return counter
def get_counter():
return counter # Can read global without declaration
print(increment_counter()) # Output: 1
print(get_counter()) # Output: 1
Enclosing scope refers to variables defined in outer functions that are accessible to inner functions.
In nested functions, inner functions can access variables from their enclosing scope. To modify these variables, you need to use the nonlocal keyword.
def outer_function():
message = "Hello" # Enclosing scope variable
def inner_function():
nonlocal message # Declare we're using the enclosing variable
message = "Hi"
return message
print(message) # Output: Hello
inner_function()
print(message) # Output: Hi
outer_function()
Built-in scope contains Python's pre-defined names like print(), len(), and other built-in functions and types.
Built-in names are always available and don't need to be imported. They can be overridden in other scopes, but it's generally not recommended.
# Built-in names are always available
print(len([1, 2, 3])) # Output: 3
# Can override built-in names (not recommended)
len = 5
print(len) # Output: 5
# Restore built-in name
del len
print(len([1, 2, 3])) # Output: 3
The LEGB rule determines the order in which Python looks for variables: Local, Enclosing, Global, and Built-in scopes.
Python follows the LEGB rule when resolving variable names. It first checks the local scope, then enclosing scope, then global scope, and finally the built-in scope.
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(f"Inner x: {x}") # Output: Inner x: local
inner()
print(f"Outer x: {x}") # Output: Outer x: enclosing
outer()
print(f"Global x: {x}") # Output: Global x: global
A closure is a function that remembers and can access variables from its enclosing scope, even after the outer function has finished executing.
Closures are created when a nested function references a variable from its enclosing scope. The inner function maintains access to these variables even after the outer function returns.
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
# Create two independent counters
counter1 = create_counter()
counter2 = create_counter()
print(counter1()) # Output: 1
print(counter1()) # Output: 2
print(counter2()) # Output: 1
Decorators use nested functions and closures to modify or enhance the behavior of other functions.
Decorators often use closures to maintain state or access variables from their enclosing scope. This allows them to modify function behavior while preserving access to the original function's scope.
def count_calls(func):
calls = 0 # Enclosing scope variable
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
print(f"Function {func.__name__} called {calls} times")
return func(*args, **kwargs)
return wrapper
@count_calls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Output: Function greet called 1 times
print(greet("Bob")) # Output: Function greet called 2 times
Classes have their own scope for class variables, while instance methods have access to both class and instance variables.
Class variables are shared among all instances, while instance variables are unique to each instance. Methods can access both through self and the class name.
class Counter:
total_count = 0 # Class variable
def __init__(self):
self.count = 0 # Instance variable
def increment(self):
self.count += 1
Counter.total_count += 1
return self.count
counter1 = Counter()
counter2 = Counter()
print(counter1.increment()) # Output: 1
print(counter2.increment()) # Output: 1
print(Counter.total_count) # Output: 2
Modules have their own global scope, and variables defined at the module level are accessible to all functions within that module.
Module-level variables act as global variables within the module. They can be imported and accessed by other modules, but modifications should be done carefully.
# config.py
DEBUG = True
API_KEY = "secret"
def get_config():
return {
"debug": DEBUG,
"api_key": API_KEY
}
# main.py
from config import DEBUG, get_config
def process_data():
if DEBUG:
print("Debug mode enabled")
config = get_config()
return config
Best practices for managing variable scope in Python programs.
Following scope best practices helps create more maintainable and predictable code. This includes minimizing global variables, using appropriate scope levels, and being explicit about variable declarations.
# Good practice: Use function parameters and return values
def calculate_total(items, tax_rate):
subtotal = sum(items)
tax = subtotal * tax_rate
return subtotal + tax
# Good practice: Use class attributes instead of globals
class Configuration:
def __init__(self):
self.debug = False
self.api_key = None
def update_config(self, debug, api_key):
self.debug = debug
self.api_key = api_key
# Good practice: Use context managers for resource management
def process_file(filename):
with open(filename, 'r') as file:
return file.read()