The history is that in Python, resource management (files, connections, transactions) uses the with statement based on the context manager protocol (enter, exit). For simple cases, writing a whole class is excessive, so the @contextmanager decorator (from the contextlib module) was proposed, allowing resource managers to be defined as generators.
Problem — manually releasing or closing resources is inconvenient, and the code becomes error-prone (for example, forgetting to close a file). Also, you don't want to write a separate class with two methods just for simple tasks (like temporarily changing the directory or stdout).
Solution — use @contextmanager to succinctly describe the "beginning" and "end" of using a resource, ensuring exceptions and releases are handled properly.
Example code:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Key features:
Can you make it so that yield from @contextmanager returns multiple objects (for example, through a tuple)?
Yes, you can, and it is even convenient for returning a "group" of related resources.
Example code:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
What happens if an exception is raised after yield — will the resource close?
Yes, the finally block will execute in any case, even if an error/exception occurs in the code inside the with statement.
Can @contextmanager replace a full-fledged context manager class with enter/exit?
In most trivial cases — yes, for more complex cases with nested states or inheritance, it’s more convenient to work through a class.
Manually opening and closing a file:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Pros:
Cons:
Using @contextmanager for temporarily changing the working directory or opening a file (or environment settings):
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Pros:
Cons: