Direct naar de hoofdinhoud
Module 9 · Les 3 van 435/36 in de cursus~12 min
Modulelessen (3/4)

Contextmanager: met en __enter__/__exit__

Een contextmanager is een object dat u gebruikt met with. Het garandeert dat bepaalde installatie-/opruimbewerkingen vinden altijd plaats, zelfs als er fouten optreden. Het canonieke voorbeeld: een bestand openen en sluiten.

Het with patroon

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

In gewoon Engels: "met bron X geopend als f, doe dit. Als je klaar bent (wat er ook gebeurt), sluit X." Zonder with zou je moeten schrijven elke keer een try/finally.

Meer dan één tegelijk

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

Je eigen contextmanager schrijven (klas)

U moet twee dunder-methoden definiëren: __enter__ en __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) wordt aangeroepen bij het betreden van het blok; de geretourneerde waarde is wat je krijgt na as.
  • __exit__(self, exc_type, exc_value, tb) wordt gebeld bij het verlaten (altijd). Als het True retourneert, worden eventuele uitzonderingen onderdrukt.

De snelkoppeling: @contextmanager

Met contextlib.contextmanager kun je een contextmanager schrijven als generatorfunctie:

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))

De code voor yield is __enter__, de code na is __exit__. De try/finally garandeert opschoning, zelfs als er fouten optreden.

onderdrukken: een uitzondering negeren

Python
from contextlib import suppress

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

Valkuil: with alleen voor objecten die dit ondersteunen

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

Contextmanagers schrijven met @contextmanager

De module contextlib van de standaardbibliotheek exporteert een zeer handige decorateur genaamd @contextmanager om contextmanagers te maken met behulp van generatorfuncties, in plaats van uitgebreide klassen te schrijven die de methoden __enter__ en __exit__ implementeren. Je schrijft gewoon een generatorfunctie die een enkele yield gebruikt:

Python
from contextlib import contextmanager

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

Probeer het zelf

Oefening#python.m9.l3.e1
Pogingen: 0Laden…

Gebruik contextlib.suppress om int('non-numero') te proberen en ValueError te negeren. Wijs na het blok `ok = True` toe. Evalueer 'oké'.

Editor laden…
Toon hint

suppress(ValueError) slikt alleen ValueError in.

Oplossing beschikbaar na 3 pogingen

Oefening beoordelen

Oefening#python.m9.l3.e2
Pogingen: 0Laden…

Schrijf een contextmanager `collect` met @contextmanager die een lege lijst oplevert en deze bij het afsluiten omzet in een tupel (print deze af). Gebruik het om 1, 2, 3 toe te voegen aan de lijst in het blok. Evalueer aan het einde `len(collection)` waarbij `collection` de resulterende lijst is.

Editor laden…
Toon hint

items opleveren om de lijst binnen het with-blok bloot te leggen.

Oplossing beschikbaar na 3 pogingen

Extra uitdaging

Oefening#python.m9.l3.e3
Pogingen: 0Laden…

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():`.

Editor laden…
Toon hint

Gebruik yield binnen mock_session om de uitvoering op te schorten voordat u 'stop' toevoegt.

Oplossing beschikbaar na 3 pogingen