Skip to content

Latest commit

 

History

History
312 lines (221 loc) · 7.8 KB

File metadata and controls

312 lines (221 loc) · 7.8 KB

Understanding Variable Scope and Lifetime in Python

Slide 1: Understanding Variable Scope in Python

Variable scope determines where in your code a variable can be accessed. Python has different types of scope, including local, global, and nonlocal. Let's explore these concepts with practical examples.

x = 10

def print_x():
    # Local variable
    x = 20
    print(f"Local x: {x}")

print(f"Global x: {x}")
print_x()
print(f"Global x after function call: {x}")

# Output:
# Global x: 10
# Local x: 20
# Global x after function call: 10

Slide 2: Local Scope

Variables defined inside a function have local scope. They are only accessible within that function and do not affect variables with the same name outside the function.

    message = f"Hello, {name}!"
    print(message)

greet("Alice")
# This will raise a NameError
# print(message)

# Output:
# Hello, Alice!
# NameError: name 'message' is not defined

Slide 3: Global Scope

Global variables are defined outside of any function and can be accessed throughout the entire script. However, modifying them inside a function requires the 'global' keyword.

def increment():
    global count
    count += 1
    print(f"Count inside function: {count}")

increment()
print(f"Count outside function: {count}")

# Output:
# Count inside function: 1
# Count outside function: 1

Slide 4: Nonlocal Scope

Nonlocal scope is used in nested functions to refer to variables in the enclosing (but non-global) scope. It allows modification of variables in the outer function from within the inner function.

    x = "outer"
    
    def inner():
        nonlocal x
        x = "inner"
        print(f"x inside inner(): {x}")
    
    inner()
    print(f"x inside outer(): {x}")

outer()

# Output:
# x inside inner(): inner
# x inside outer(): inner

Slide 5: Built-in Scope

Python's built-in scope contains names that are always available, such as print(), len(), and range(). These can be used anywhere in your code without importing them.

numbers = [1, 2, 3, 4, 5]
print(f"Length of numbers: {len(numbers)}")
print(f"Sum of numbers: {sum(numbers)}")
print(f"Maximum value: {max(numbers)}")

# Output:
# Length of numbers: 5
# Sum of numbers: 15
# Maximum value: 5

Slide 6: Variable Lifetime

A variable's lifetime starts when it's created and ends when it's destroyed. In Python, this is typically managed automatically through reference counting and garbage collection.

    x = 10  # x is created
    print(f"x inside function: {x}")
    # x goes out of scope and is eligible for garbage collection

demonstrate_lifetime()
# This will raise a NameError
# print(x)

# Output:
# x inside function: 10
# NameError: name 'x' is not defined

Slide 7: Scope Resolution (LEGB Rule)

Python follows the LEGB rule for scope resolution: Local, Enclosing, Global, Built-in. This determines the order in which Python looks for variables.

def outer():
    x = "outer"
    
    def inner():
        x = "inner"
        print(f"x in inner: {x}")
    
    inner()
    print(f"x in outer: {x}")

outer()
print(f"x in global: {x}")

# Output:
# x in inner: inner
# x in outer: outer
# x in global: global

Slide 8: Global vs. Local Variables: Performance Considerations

Using global variables can sometimes lead to unexpected behavior and make code harder to debug. Local variables are generally faster to access and safer to use.

def test_global():
    global x
    x = 10
    for i in range(1000000):
        x += 1

def test_local():
    x = 10
    for i in range(1000000):
        x += 1

print(f"Global variable time: {timeit.timeit(test_global, number=1)}")
print(f"Local variable time: {timeit.timeit(test_local, number=1)}")

# Output may vary, but local is typically faster:
# Global variable time: 0.1234567
# Local variable time: 0.0987654

Slide 9: Closures and Variable Scope

Closures are functions that remember the environment in which they were created. They can access variables from the enclosing scope even after the outer function has finished executing.

    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(f"Double 5: {double(5)}")
print(f"Triple 5: {triple(5)}")

# Output:
# Double 5: 10
# Triple 5: 15

Slide 10: Namespace and Scope

In Python, a namespace is a mapping from names to objects. Different namespaces can co-exist without conflict, and they are created at different moments and have different lifetimes.

x = 10

def outer():
    # Enclosing namespace
    x = 20
    
    def inner():
        # Local namespace
        x = 30
        print(f"Local x: {x}")
    
    inner()
    print(f"Enclosing x: {x}")

outer()
print(f"Global x: {x}")

# Output:
# Local x: 30
# Enclosing x: 20
# Global x: 10

Slide 11: The 'global' Statement: Best Practices

While the 'global' statement allows modification of global variables within a function, it's generally considered a bad practice. It can lead to confusing code and make debugging difficult.

def bad_increment():
    global counter
    counter += 1

def good_increment(c):
    return c + 1

# Bad practice
bad_increment()
print(f"Counter after bad_increment: {counter}")

# Good practice
counter = good_increment(counter)
print(f"Counter after good_increment: {counter}")

# Output:
# Counter after bad_increment: 1
# Counter after good_increment: 2

Slide 12: Real-life Example: Configuration Management

Imagine you're building a web application that needs to access configuration settings throughout different parts of the code. Using a global configuration object can be a practical solution.

class Config:
    DEBUG = False
    DATABASE_URI = "sqlite:///example.db"

# main.py
from config import Config

def setup_database():
    print(f"Setting up database with URI: {Config.DATABASE_URI}")

def run_app():
    if Config.DEBUG:
        print("Running in debug mode")
    else:
        print("Running in production mode")

setup_database()
run_app()

# Output:
# Setting up database with URI: sqlite:///example.db
# Running in production mode

Slide 13: Real-life Example: Logger Implementation

Implementing a logger that can be used across different modules is another practical application of global scope. This allows for consistent logging throughout your application.

# Set up global logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def process_data(data):
    logger.info(f"Processing data: {data}")
    # Simulate data processing
    result = data.upper()
    logger.info(f"Processed result: {result}")
    return result

def main():
    input_data = "hello, world"
    logger.info(f"Starting main function with input: {input_data}")
    output = process_data(input_data)
    logger.info(f"Main function completed. Output: {output}")

if __name__ == "__main__":
    main()

# Output:
# INFO:__main__:Starting main function with input: hello, world
# INFO:__main__:Processing data: hello, world
# INFO:__main__:Processed result: HELLO, WORLD
# INFO:__main__:Main function completed. Output: HELLO, WORLD

Slide 14: Additional Resources

For those interested in diving deeper into Python's variable scope and lifetime, here are some valuable resources:

  1. Python's official documentation on naming and binding: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding
  2. "Fluent Python" by Luciano Ramalho - A comprehensive book that covers Python's data model, including scoping rules.
  3. "Python Cookbook" by David Beazley and Brian K. Jones - Provides practical recipes for working with Python, including advanced uses of scoping.
  4. Online Python courses on platforms like Coursera, edX, or Udacity that cover advanced Python concepts.