Web Analytics

Context Managers

Advanced ~25 min read

Context managers provide a clean way to manage resources that need setup and cleanup. Using the with statement ensures resources are properly acquired and released, even if exceptions occur. This eliminates the need for try/finally blocks and makes your code more readable and robust!

The with Statement

The with statement is Python's way of using context managers. When you use with, Python automatically calls setup code (enter) before the block and cleanup code (exit) after, even if an exception occurs. The most common example is file handling!

Output
Click Run to execute your code
How with Works:
1. The expression after with must return a context manager
2. Python calls __enter__() before the block executes
3. The return value of __enter__() is assigned to the variable after as
4. The code block executes
5. Python calls __exit__() after the block, even if an exception occurred
6. __exit__() receives exception info if one occurred

The Context Manager Protocol

To create a custom context manager, you need a class that implements __enter__() and __exit__() methods. The __enter__ method runs setup code and optionally returns a value. The __exit__ method handles cleanup.

Output
Click Run to execute your code
Pro Tip: The __exit__ method receives three arguments when an exception occurs: exception type, exception value, and traceback. Returning True from __exit__ suppresses the exception (use sparingly)!

Multiple Context Managers

You can use multiple context managers in a single with statement, either on separate lines or using commas. This is perfect for managing multiple resources simultaneously!

Output
Click Run to execute your code

Using @contextmanager

The contextlib module provides the @contextmanager decorator, which lets you create context managers using generator functions. This is often simpler than creating a full class!

Output
Click Run to execute your code
Important: With @contextmanager, everything before yield is setup code (like __enter__), and everything after yield is cleanup code (like __exit__). If an exception occurs, Python continues after yield to run cleanup, so use try/finally if needed!

Common Mistakes

1. Not using with for resources

# Wrong - file might not close if exception occurs
file = open("data.txt")
content = file.read()
# What if exception happens here?
file.close()  # Might not execute!

# Correct - use with statement
with open("data.txt") as file:
    content = file.read()
# File automatically closed, even if exception occurs

2. Forgetting to handle exceptions in __exit__

# Wrong - cleanup might fail and hide original exception
class MyContext:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cleanup()  # If this raises, original exception lost!

# Correct - handle cleanup exceptions
class MyContext:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.cleanup()
        except Exception:
            pass  # Log but don't raise
        return False  # Don't suppress original exception

3. Returning True from __exit__ unintentionally

# Wrong - suppresses all exceptions!
class SuppressContext:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        return True  # Suppresses ALL exceptions!

with SuppressContext():
    raise ValueError("This error is hidden!")  # Won't propagate!

# Correct - only suppress specific exceptions
class SuppressContext:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type == ValueError:  # Only suppress ValueError
            return True  # Suppress this specific exception
        return False  # Let others propagate

Exercise: Create a Timer Context Manager

Task: Create a custom context manager class that measures execution time. Use __enter__ and __exit__ methods, and print the elapsed time when exiting.

Requirements:

  • Import the time module
  • Create a Timer class with __enter__ and __exit__ methods
  • Record start time in __enter__
  • Calculate and print elapsed time in __exit__
  • Test it with the with statement and simulate some work
Output
Click Run to execute your code
Show Solution
import time

class Timer:
    """Context manager for measuring execution time."""
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.time()
        elapsed = self.end - self.start
        print(f"Elapsed time: {elapsed:.4f} seconds")
        return False  # Don't suppress exceptions


# Test the Timer context manager
with Timer():
    time.sleep(0.5)  # Simulate some work

with Timer() as timer:
    total = sum(range(1000000))
    print(f"Sum result: {total}")

Summary

  • Context Managers: Objects that manage resources with setup and cleanup code
  • with Statement: Ensures proper resource management, automatically calls __enter__ and __exit__
  • __enter__: Setup method called before the block, return value assigned to variable after as
  • __exit__: Cleanup method called after the block, receives exception info if one occurred
  • Exception Handling: __exit__ runs even if exceptions occur; return True to suppress exceptions
  • @contextmanager: Decorator from contextlib to create context managers from generator functions
  • Multiple Contexts: Use commas or separate lines in with statement for multiple resources
  • Common Uses: File handling, database connections, locks, temporary state changes, timing/debugging

What's Next?

Context managers are essential for proper resource management! Next, we'll explore regular expressions (regex), powerful pattern matching tools that let you search, extract, and manipulate text with complex patterns. Regex is invaluable for text processing, validation, and data extraction!