Contextmanagers maken het mogelijk om het uitvoeren van codeblokken te automatiseren en te controleren met gegarandeerde finalisatie van middelen — bijvoorbeeld, automatisch bestanden sluiten, database transaction managers vrijgeven of threads blokkeren/ontgrendelen. In de standaard Python is dit paradigma geïntroduceerd voor veilige en handige beheersing van externe middelen zonder dat men handmatig de laatste fase van het werk hoeft bij te houden.
Geschiedenis:
Voor de invoering van contextmanagers was het nodig om expliciet de close()/release() methoden aan te roepen voor het vrijgeven van middelen, wat leidde tot fouten als er uitzonderingen optraden. Met de komst van de constructie with ... as ...: stelde Python in staat om "de scope van het middel" expliciet te definiëren, waarbij methoden voor initiatie en voltooiing automatisch werden aangeroepen.
Probleem:
Handmatige controle over het "sluiten" van middelen is riskant — als men vergeet om close() (of release()) aan te roepen, blijven middelen bezet tot het proces eindigt of kunnen ze zelfs voor altijd vastlopen. Dit is vooral acuut bij het werken met bestanden, netwerkverbindingen en database transacties.
Oplossing:
Contextmanagers worden geïmplementeerd via de magische methoden enter() en exit(). Bij het binnenkomen van de with blok roept Python enter aan, en bij het verlaten exit, zelfs als er binnen het blok een uitzondering optreedt.
Voorbeeldcode:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # U kunt de uitzondering onderdrukken door return True; normaal return False with ManagedFile('demo.txt') as f: f.write('Hello, world!\n') # bestand is gegarandeerd gesloten
Belangrijke kenmerken:
Kan dezelfde instantie van een contextmanager meerdere keren achtereenvolgens in verschillende with gebruiken?
Nee: meestal wordt het middel geopend in enter, vrijgegeven in exit, en het object wordt ongeldig voor herbruik. Het is beter om een nieuw object te maken voor elk with blok.
Wat te doen als u meerdere middelen in één with wilt gebruiken?
U kunt de variabelen scheiden met een komma:
with open('a.txt') as fa, open('b.txt') as fb: ...
of gebruik contextlib.ExitStack voor complexe gevallen.
Wat is het verschil tussen het schrijven van een contextmanager als klasse met enter/exit en als generator met de @contextmanager decorator?
De @contextmanager decorator uit de contextlib-module maakt het gemakkelijker om de manager te implementeren via yield:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
Een ontwikkelaar opent een bestand via open(), schrijft erin, en vergeet close() aan te roepen — het bestand kan open blijven (vooral bij fouten), gegevens worden niet geschreven.
Voordelen:
Nadelen:
Overal is with open() als standaard (of custom managed resource) gebruikt, de contextmanager implementeert correct het vrijgeven van middelen.
Voordelen:
Nadelen: