Function Scope

Master the concept of variable scope in Python functions and understand how variables are accessed and modified within different scopes.

45 minutes Intermediate Functions

Local Scope

Understanding Local Variables

Definition

Local scope refers to variables defined inside a function that are only accessible within that function.

Explanation

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

Working with Global Variables

Definition

Global scope refers to variables defined at the module level that are accessible throughout the entire program.

Explanation

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

Understanding Nested Functions

Definition

Enclosing scope refers to variables defined in outer functions that are accessible to inner functions.

Explanation

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

Python's Built-in Names

Definition

Built-in scope contains Python's pre-defined names like print(), len(), and other built-in functions and types.

Explanation

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

Scope Resolution

LEGB Rule

Definition

The LEGB rule determines the order in which Python looks for variables: Local, Enclosing, Global, and Built-in scopes.

Explanation

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

Scope and Closures

Creating Closures

Definition

A closure is a function that remembers and can access variables from its enclosing scope, even after the outer function has finished executing.

Explanation

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

Scope and Decorators

Variable Scope in Decorators

Definition

Decorators use nested functions and closures to modify or enhance the behavior of other functions.

Explanation

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

Scope and Classes

Variable Scope in Classes

Definition

Classes have their own scope for class variables, while instance methods have access to both class and instance variables.

Explanation

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

Scope and Modules

Module-Level Scope

Definition

Modules have their own global scope, and variables defined at the module level are accessible to all functions within that module.

Explanation

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

Scope Best Practices

Guidelines for Variable Scope

Definition

Best practices for managing variable scope in Python programs.

Explanation

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()
Concept 1 of 10