🧡 Python Tutorial β€” Context Managers (with Statement)

Introduction 🌟

A Context Manager allows you to manage resources efficiently and safely using the with statement. It ensures proper setup and cleanup β€” even if errors occur.

Note

πŸ’‘ Commonly used for file handling, database connections, locks, network sessions
πŸ’‘ Guarantees cleanup β†’ prevents resource leaks
πŸ’‘ Implemented using __enter__ and __exit__

1. Why Use Context Managers? πŸ€”

Without context manager:

without_with.py

f = open("data.txt", "r")
content = f.read()
f.close()  # must remember to close

With context manager:

with_statement.py

with open("data.txt", "r") as f:
    content = f.read()

βœ” Automatically closes the file
βœ” Cleaner & safer

2. How the with Statement Works 🧠

The with statement calls two special methods of the context manager:

  • πŸ”Ή __enter__() β†’ executed before the block starts
  • πŸ”Ή __exit__() β†’ executed after the block ends (even on error)

3. Creating Your Own Context Manager (Class-Based) πŸ—οΈ

custom_cm_class.py

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return "Resource"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exiting context...")
        print("Error:", exc_type)
        return False  # return True to suppress errors

with MyContext() as r:
    print("Using:", r)

βœ” You now understand how context managers actually work!

4. Using contextlib β€” Function-Based Context Manager 🧩

Instead of creating a class, Python allows a simpler way using decorators.

contextlib_example.py

from contextlib import contextmanager

@contextmanager
def my_cm():
    print("Enter")
    yield "Resource"
    print("Exit")

with my_cm() as r:
    print("Using:", r)

βœ” Cleaner and easier to write

5. Real-World Example β€” File Handling πŸ“

file_handling.py

with open("data.txt", "w") as file:
    file.write("Hello World")

βœ” File is automatically closed

6. Real-World Example β€” Database Connection πŸ—„οΈ

db_connection.py

class DB:
    def connect(self): print("Connected")
    def close(self): print("Closed")

class DBManager:
    def __enter__(self):
        self.db = DB()
        self.db.connect()
        return self.db

    def __exit__(self, *args):
        self.db.close()

with DBManager() as db:
    print("Working with DB...")

7. Real-World Example β€” Timing Code Execution ⏱️

timer_cm.py

import time
from contextlib import contextmanager

@contextmanager
def timer():
    start = time.time()
    yield
    end = time.time()
    print("Elapsed:", end - start)

with timer():
    for _ in range(1_000_000):
        pass

8. Real-World Example β€” Suppressing Exceptions πŸ™ˆ

suppress_exception.py

from contextlib import suppress

with suppress(ZeroDivisionError):
    print(10 / 0)  # error is ignored

βœ” Only suppresses specified errors

9. Real-World Example β€” Redirect stdout πŸ“€

redirect_stdout.py

from contextlib import redirect_stdout

with open("log.txt", "w") as f:
    with redirect_stdout(f):
        print("This goes to the file")

10. Real-World Example β€” Closing Multiple Resources ✨

exit_stack.py

from contextlib import ExitStack

with ExitStack() as stack:
    f1 = stack.enter_context(open("a.txt"))
    f2 = stack.enter_context(open("b.txt"))
    print("Both files opened safely")

βœ” Manages multiple context managers elegantly

11. How Exceptions Work in Context Managers ⚠️

exception_handling.py

class CM:
    def __enter__(self):
        print("Enter")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exit")
        if exc_type:
            print("Error occurred:", exc_type)
        return False  # re-raise error

βœ” Returning True suppresses the exception
βœ” Returning False re-raises it

Context Manager Cheat Sheet πŸ“˜

Method / ToolPurpose
__enter__()Setup before block begins
__exit__()Cleanup after block ends
withAutomatically handles setup/cleanup
@contextmanagerCreate simple context managers
suppressIgnore specific exceptions
redirect_stdoutRedirect output
ExitStackManage multiple resources

Best Practices πŸ’‘

  • βœ” Always use context managers for files, DB connections, network calls
  • βœ” Use custom context managers to manage reusable resources
  • βœ” Use @contextmanager for simpler contexts
  • βœ” Use ExitStack for dynamic or multiple resources
  • βœ” Avoid suppressing errors unless absolutely needed

Conclusion πŸŽ‰

>>β€œContext managers allow Python to manage resources cleanly, safely, and beautifully β€” with just one keyword: with.” ✨

You now fully understand Context Managers in Python! Want the next topic? Try Decorators, Generators, Exception Handling, or File Handling. Just tell me! 😊