Context manager (with)

Context manager (with)

  • 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 42
from 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")

References

SuperMade with Super