- The main problem WITH solves in making sure to release resources after usage. (Facilitate the proper handling of resources.)
- TRY / EXCEPT / FINALLY also solves this problem.
- Create CM using Classes __enter__() and __exit__().
The __enter__() returns the resource that needs to be managed and the __exit__() does not return anything but performs the cleanup operations.
import contextlib
@contextlib.contextmanager
def my_context():
print('hello')
yield 42
print('finished')
with my_context() as foo: # we use 'as' cuz my_context kind of returns yield 42
print(f'foo is {foo}') # foo is 42from contextlib import contextmanager
@contextmanager
def timed(label):
t = time.perf_counter()
try:
yield
finally:
print(label, time.perf_counter() - t)Difference with @decorators
Feature | Context Manager (with) | Decorator (@) |
Target | A block of code | An entire function/class |
Control | Fine-grained (mid-function) | Coarse (function-level) |
Variables | Can return a value to the local scope ( as) | Usually returns the function itself |
Common Use | Files, DBs, Locks, Temp changes | Logging, Auth, Caching, Validation |
Use a Context Manager if:
You need to ensure a resource is cleaned up, or you only want the "wrapping" to happen to a few lines of code rather than the whole function.
Example: You only want to suppress an error for one specific line, not the whole function.
Use a Decorator if:
You want to apply the same logic to many different functions.
Example: You want to ensure the user is logged in before running any of these 10 different API functions.
from contextlib import ContextDecorator
class my_wrapper(ContextDecorator):
def __enter__(self):
print("Starting")
return self
def __exit__(self, *exc):
print("Finishing")
@my_wrapper()
def function_a():
print("Inside function")
# OR
with my_wrapper():
print("Inside block")