2.24. Context managers

De with-instructie draait setup-code, daarna een body, daarna teardown-code – met de garantie dat de teardown draait, zelfs wanneer de body halverwege faalt. Het paar methoden dat de setup en teardown levert, wordt een context manager genoemd.

De vorm is:

with <expression> as <name>:
    <body>

De expressie geeft een context manager terug. Python roept zijn __enter__-methode aan, bindt optioneel het resultaat aan <name>, draait de body, en roept vervolgens __exit__ aan – of de body nu normaal voltooide of een uitzondering opwierp.

Stroomdiagram: __enter__ draait eerst, daarna de body; de body voltooit ofwel normaal of werpt op; hoe dan ook, draait __exit__ aan het einde.

__exit__ draait aan het einde van het blok, wat er ook binnenin gebeurt.

2.24.1. Een context manager gebruiken

Het canonieke voorbeeld is het openen van een bestand:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() geeft een bestandsobject terug dat zelf een context manager is; __enter__ geeft het bestand terug, en __exit__ sluit het. Het with-blok maakt van “sluit het bestand altijd af wanneer je klaar bent” de standaard in plaats van iets dat de aanroeper moet onthouden.

2.24.1.1. Meerdere context managers

Een enkele with-instructie kan meerdere context managers tegelijk binnengaan, gescheiden door komma’s:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Equivalent aan twee geneste with-blokken, maar platter. De managers worden van links naar rechts binnengegaan en in omgekeerde volgorde verlaten; als __enter__ op de rechtermanager opwerpt, draait de __exit__ van de linkermanager nog steeds.

2.24.2. Een context manager schrijven

Elke klasse met __enter__- en __exit__-methoden werkt als een context manager:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Uitvoer:

--- setup ---
doing the work
--- end setup ---

__exit__ ontvangt drie argumenten die de uitzondering beschrijven die het blok beëindigde, of drie None-waarden als het blok normaal voltooide. Het teruggeven van False (of None) laat elke uitzondering na de teardown doorvloeien; het teruggeven van True zou die opslokken.

Gebruik context managers voor elke resource die een levenscyclus van “openen / sluiten” of “verkrijgen / vrijgeven” heeft – niet alleen bestanden. Het patroon houdt opruiming gekoppeld aan setup op het punt waar beide worden geïntroduceerd, zodat een vergeten sluiting midden in een lange functie de resource niet kan lekken.