De geschiedenis - in Python wordt de with-constructie gebruikt voor resourcebeheer (bestanden, verbindingen, transacties), gebaseerd op het contextmanager-protocol (enter, exit). Voor eenvoudige gevallen is het overbodig om een hele klasse te schrijven, daarom is de @contextmanager decorator (contextlib module) voorgesteld, waarmee resourcemanagers als generators kunnen worden gedefinieerd.
Probleem - handmatig resources vrijgeven of sluiten is onhandig, de code wordt kwetsbaar voor fouten (bijvoorbeeld, vergeten een bestand te sluiten). Ook wil je niet voor eenvoudige dingen (zoals tijdelijke wijziging van de directory of stdout) een aparte klasse met twee methoden schrijven.
Oplossing - gebruik @contextmanager om de "begin" en "einde" van het gebruik van een resource bondig te beschrijven, met gegarandeerde uitzondering- en vrijgaveverwerking.
Codevoorbeeld:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Belangrijke kenmerken:
Is het mogelijk om meerdere objecten vanuit yield in @contextmanager te retourneren (bijvoorbeeld via een tuple)?
Ja, dat kan en het is zelfs handig voor het retourneren van een "groep" gerelateerde resources.
Codevoorbeeld:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
Wat gebeurt er als er na yield een uitzondering wordt opgegooid - wordt de resource gesloten?
Ja, het finally-blok wordt altijd uitgevoerd, zelfs als er een fout/uitzondering optreedt in de code binnen with.
Kan @contextmanager een volwaardige contextmanagerklasse met enter/exit vervangen?
In de meeste triviale gevallen - ja, voor complexere gevallen met geneste staten of overerving werkt het handiger via een klasse.
Handmatig een bestand openen en sluiten:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Voordelen:
Nadelen:
Gebruik @contextmanager voor tijdelijke wijziging van de werkdirectory of het openen van een bestand (of omgevingsinstellingen):
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Voordelen:
Nadelen: