Skip to main content
eLearner.app
Module 9 · Lesson 3 of 435/36 in the course~12 min
Module lessons (3/4)

Context manager: with and __enter__/__exit__

A context manager is an object you use with with. It guarantees that certain setup/cleanup operations always happen, even when errors occur. The canonical example: opening and closing a file.

The with pattern

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

In plain English: "with resource X open as f, do this. When done (whatever happens), close X." Without with you would have to write a try/finally every time.

More than one at a time

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

Writing your own context manager (class)

You need to define two dunder methods: __enter__ and __exit__.

Python
class Timer:
    def __enter__(self):
        import time
        self.t0 = time.perf_counter()
        return self  # è l'oggetto che finisce dopo "as"
    def __exit__(self, exc_type, exc_value, traceback):
        import time
        self.elapsed = time.perf_counter() - self.t0
        # ritornare True ingoia l'eccezione; di solito non lo vuoi
        return False

with Timer() as t:
    sum(range(10**6))
t.elapsed  # quanto è durato
  • __enter__(self) is called on entering the block; the returned value is what you get after as.
  • __exit__(self, exc_type, exc_value, tb) is called on exit (always). If it returns True, any exceptions are suppressed.

The shortcut: @contextmanager

contextlib.contextmanager lets you write a context manager as a generator function:

Python
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    t0 = time.perf_counter()
    try:
        yield  # qui sta il blocco "with"
    finally:
        elapsed = time.perf_counter() - t0
        print(f"durata: {elapsed:.4f}s")

with timer():
    sum(range(10**6))

The code before yield is __enter__, the code after is __exit__. The try/finally guarantees cleanup even when errors occur.

suppress: ignore an exception

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

Pitfall: with only for objects that support it

Python
x = 42
with x:  # AttributeError: __enter__
    ...

Writing context managers with @contextmanager

The standard library's contextlib module exports a highly convenient decorator called @contextmanager to create context managers using generator functions, instead of writing verbose classes implementing __enter__ and __exit__ methods. You just write a generator function that uses a single yield:

Python
from contextlib import contextmanager

@contextmanager
def manage_resource():
    # __enter__ code
    yield resource
    # __exit__ code

Try it yourself

Exercise#python.m9.l3.e1
Attempts: 0Loading…

Use contextlib.suppress to try int('non-numero') ignoring ValueError. After the block assign `ok = True`. Evaluate `ok`.

Loading editor…
Show hint

suppress(ValueError) swallows only ValueError.

Solution available after 3 attempts

Review exercise

Exercise#python.m9.l3.e2
Attempts: 0Loading…

Write a context manager `collect` with @contextmanager that yields an empty list and, on exit, converts it to a tuple (print it). Use it to append 1, 2, 3 to the list inside the block. At the end, evaluate `len(collection)` where `collection` is the yielded list.

Loading editor…
Show hint

yield items to expose the list inside the with block.

Solution available after 3 attempts

Additional challenge

Exercise#python.m9.l3.e3
Attempts: 0Loading…

Write a custom context manager using the `@contextmanager` decorator from `contextlib`. The context manager must be named `mock_session`. Upon entry, it must append `'start'` to a global list `log_list = []`, yield control using `yield`, and upon exit append `'stop'`. Finally, evaluate `log_list` after using the context manager with `with mock_session():`.

Loading editor…
Show hint

Use yield inside mock_session to suspend execution before appending 'stop'.

Solution available after 3 attempts