Python Tutorial: Context Managers - Efficiently Managing Resources
Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.
Use `with` to tie resource lifetime to a block so teardown runs automatically when the block exits, including on exceptions.
Briefing
Context managers in Python make resource handling reliable by guaranteeing setup and teardown happen automatically—even when errors occur. Instead of manually opening something and remembering to close it later, the `with` statement ties the lifetime of a resource to a block of code, so cleanup runs when the block ends. That reliability matters most for resources like files, database connections, and locks, where forgetting teardown can cause leaks, corrupted state, or lingering locks.
The tutorial first demonstrates the classic file case. It contrasts a manual approach—open a file, write to it, then explicitly close it—with a context-manager approach using `with open(...) as f:`. The key benefit is that the file gets closed properly even if an exception interrupts the block. From there, the focus shifts to building custom context managers so the same automatic cleanup can be applied to user-defined resources.
One custom implementation uses a class. A class-based context manager defines three special methods: `__init__` to store parameters like `file_name` and `mode`, `__enter__` to perform setup (open the file and return the file object), and `__exit__` to perform teardown (close the file). When used like `with OpenFile(sample.txt, 'w') as f:`, the `with` statement calls `__enter__`, binds the returned file object to `f`, runs the block, and then calls `__exit__` to close the file. The tutorial even checks `f.closed` after leaving the block to confirm teardown happened.
A second implementation shows how to write context managers using a function decorated with `contextlib.contextmanager`. Here, the function is a generator: everything before `yield` acts like setup, the `yield` point hands control to the `with` block, and everything after `yield` acts like teardown. The example mirrors the file workflow: open the file before `yield`, yield the file object to the block, and close it after the block finishes. To make teardown robust under exceptions, the tutorial adds a `try`/`finally` structure so cleanup runs even when errors occur.
Finally, the tutorial builds a practical context manager for changing directories. Without a context manager, code typically saves the current working directory, calls `os.chdir(destination)`, performs work, and then switches back—repeated every time. The custom generator-based context manager captures the original directory in setup, changes to the destination, yields control so arbitrary work can run in that directory, and then restores the original directory in teardown. Using `with change_dir(sample_dir_1):` and `with change_dir(sample_dir_2):`, the directory switching becomes clean and reusable, eliminating the need to remember restoration logic.
Overall, the takeaway is that context managers provide a structured pattern for “do setup, run work, always do teardown,” and Python supports both class-based and generator-based implementations to fit different preferences and use cases.
Cornell Notes
Context managers in Python ensure resources are set up and torn down automatically when entering and exiting a `with` block. The tutorial demonstrates this reliability with files: the file is closed even if an exception occurs, removing the need for manual cleanup. It then shows two ways to create custom context managers. A class-based approach uses `__init__`, `__enter__`, and `__exit__` to open and close resources. A generator-based approach uses `@contextmanager`, where code before `yield` is setup and code after `yield` is teardown, typically guarded by `try`/`finally`. The practical example uses a context manager to temporarily change directories and always restore the original working directory.
Why does the `with` statement matter for resource management in Python?
How does a class-based context manager work internally?
What does `yield` do in a generator-based context manager?
How can teardown be made exception-safe in a generator-based context manager?
What practical problem does the directory-changing context manager solve?
Review Questions
- In a class-based context manager, which methods correspond to setup and teardown, and what does each method return or do?
- In a generator-based context manager, what is the role of `yield`, and how does code after `yield` relate to teardown?
- How does using `try`/`finally` inside a context manager change behavior when an exception occurs inside the `with` block?
Key Points
- 1
Use `with` to tie resource lifetime to a block so teardown runs automatically when the block exits, including on exceptions.
- 2
A class-based context manager uses `__init__` for parameters, `__enter__` for setup (and returning the resource), and `__exit__` for teardown.
- 3
A generator-based context manager uses `@contextmanager`; code before `yield` is setup, the `with` block runs during the `yield`, and code after `yield` is teardown.
- 4
Guard teardown with `try`/`finally` so cleanup still happens if the block raises an error.
- 5
Custom context managers can replace repetitive manual patterns like open/close and save/restore logic.
- 6
A directory-changing context manager can safely switch directories for a block of work and always restore the original working directory afterward.
- 7
Context managers generalize beyond files to resources like database connections and locks, where reliable cleanup is critical.