← Python/Interfaces en typing
Les 8 van 8
MODULE 08 · 08◷ 32 min

Een blok met openen en opruimen verbinden

Na deze les kun je
  • Enter- en exitgedrag met echte foutgegevens implementeren.
  • De enter-retourwaarde en het resultaat van een handeling onderscheiden.
  • Opruimen, foutdoorgave en gerichte onderdrukking afzonderlijk bewaken.

Sommige bewerkingen hebben een duidelijk begin en einde: je opent een lokale resource, gebruikt haar en sluit haar weer. Ook wanneer het gebruik mislukt, moet het afsluiten nog gebeuren. Een contextmanager verbindt die afspraken aan een with-blok.

Een eigen begin- en eindprotocol

Een contextmanager biedt __enter__ en __exit__ aan. De with-opdracht roept eerst __enter__ aan. De waarde die deze methode teruggeeft, komt bij de naam na as terecht.

Na het verlaten van het blok roept Python __exit__ aan, ook wanneer het blok met een return of een fout wordt verlaten. Dat geeft de contextmanager één duidelijke plaats voor opruiming. Het betekent niet dat iedere willekeurige fout vóór het binnengaan van het blok automatisch zo wordt afgehandeld.

As krijgt de resource en exit sluit na het blok af
class Session:
    def __init__(self, events):
        self.events = events
    def __enter__(self):
        self.events.append("open")
        return "lokaal"
    def __exit__(self, kind, error, traceback):
        self.events.append("dicht")
        return False

events = []
with Session(events) as name:
    print(name)
    events.append("werk")
print(events)
Verwachte uitvoer
lokaal
['open', 'werk', 'dicht']

Het voorbeeld gebruikt een eenvoudig lokaal logboek, zonder bestanden of netwerk. Je ziet daarin eerst het openen, dan de handeling in het blok en ten slotte het sluiten. De concrete objecten maken de volgorde controleerbaar.

Het resultaat van enter gebruiken

__enter__ hoeft niet de contextmanager zelf terug te geven. Hij kan ook een ontvangen resource teruggeven waarmee het blok moet werken. De opdracht with_resource moet daarom de waarde na as doorgeven aan de ontvangen functie.

Een aanroep met de manager zelf zou bij sommige klassen toevallig werken maar bij andere het verkeerde object gebruiken. De controles onderscheiden die identiteiten bewust. Het resultaat van de handeling blijft daarnaast iets anders dan de waarde van __enter__.

Weten of het blok faalde

__exit__ ontvangt drie foutgegevens naast self: het fouttype, de foutinstantie en de traceback. Bij een normale afloop zijn die alle drie None. Bij een fout beschrijven ze de echte fout uit het blok.

RecordingScope bewaart ze straks als error_type, error en traceback. De constructor met de ontvangen resource en gebeurtenissenlijst is al gegeven.

Geef een onware waarde terug wanneer de fout gewoon moet doorlopen. Een ware retourwaarde onderdrukt die fout juist. Dat is een sterke gedragskeuze, geen onbelangrijke manier om de methode af te sluiten.

Een gerichte exit onderdrukt niet iedere fout
class IgnoreKey:
    def __init__(self, events):
        self.events = events
    def __enter__(self):
        self.events.append("start")
        return self
    def __exit__(self, kind, error, traceback):
        name = "geen" if kind is None else kind.__name__
        self.events.append("sluiten:" + name)
        return isinstance(error, KeyError)

for error in [KeyError("x"), ValueError("x")]:
    events = []
    try:
        with IgnoreKey(events):
            events.append("werk")
            raise error
    except ValueError:
        events.append("buiten:ValueError")
    print(events)

events = []
with IgnoreKey(events):
    events.append("werk")
print(events)
Verwachte uitvoer
['start', 'werk', 'sluiten:KeyError']
['start', 'werk', 'sluiten:ValueError', 'buiten:ValueError']
['start', 'werk', 'sluiten:geen']

Het tweede voorbeeld onderdrukt alleen een afgesproken foutsoort. Een andere fout loopt door nadat de afsluitactie is uitgevoerd. Zo blijft zichtbaar dat opruimen en verbergen van een fout twee verschillende afspraken zijn.

Veelgemaakte fout: iedere fout ongemerkt inslikken

Een vaste return True in __exit__ laat ook onverwachte fouten verdwijnen. De aanroeper denkt dan misschien dat het blok normaal is voltooid. Gebruik die waarde alleen wanneer het concrete contract vraagt om de ontvangen fout af te handelen.

De opdracht RecordingScope onderdrukt geen fouten. Hij bewaart de ontvangen foutinformatie en registreert het afsluiten. Daardoor kan de aanroeper dezelfde oorspronkelijke exception blijven herkennen nadat het blok is verlaten.

Een with-blok werkelijk uitvoeren

Je kunt de juiste logteksten niet vervangen door alleen een lijst met vooraf bedachte resultaten. De functie with_resource moet het echte protocol van het ontvangen object gebruiken. Alleen dan bepaalt dat object welke resource wordt teruggegeven en hoe de afsluiting werkt.

Als __enter__ zelf faalt, is het blok nog niet binnengegaan en wordt __exit__ van die manager niet aangeroepen. Daarom kan de startactie zelf ook zorg nodig hebben wanneer zij gedeeltelijk iets opent. Deze les gebruikt kleine lokale fixtures om juist die protocolgrens zichtbaar te houden.

Een selectieve variant maken

Bij SuppressLookup vraagt de opdracht wel om een gerichte onderdrukking: alleen LookupError en subklassen daarvan worden als afgehandeld beschouwd. Een normale afloop blijft normaal en andere fouten lopen door. De gebruikte traceback en foutinstantie blijven de echte waarden die Python aanlevert.

Als __exit__ zelf een fout werpt, loopt die fout door, ook wanneer de handeling al een resultaat had. Was er al een bodyfout, dan blijft die via de exceptioncontext beschikbaar. Het tweede voorbeeld laat daarom ook de aparte normale afloop met drie None-gegevens zien.

AAN DE SLAG 0/4
1

Enter levert de ontvangen resource

De constructor van RecordingScope is gegeven. Vul __enter__ aan: voeg "enter" aan de ontvangen events-lijst toe en geef precies self.resource terug. Ook nul en None kunnen resources zijn; de gebeurtenissenlijst blijft dezelfde ontvangen lijst.

2

Exit registreert de echte fout en onderdrukt niets

Vul RecordingScope.__exit__(kind, error, traceback) aan. Bewaar die drie ontvangen waarden als error_type, error en traceback, voeg "exit" toe aan events en geef False terug. Normale afloop bewaart drie None-waarden; een fout loopt als dezelfde instantie door.

3

Het echte with-protocol rond een handeling gebruiken

Maak with_resource(manager, action). Voer werkelijk with manager as resource uit, roep action eenmaal met die resource aan en geef het resultaat ongewijzigd terug. Respecteer de exit van de manager bij return, bodyfout en onderdrukking. Een enter-fout voert action en exit niet uit. Een fout uit exit loopt ook door nadat action succesvol terugkeerde; bij een bodyfout gevolgd door een exit-fout is de bodyfout de exceptioncontext.

4

Alleen LookupError en subklassen onderdrukken

Vul SuppressLookup.__exit__ aan. Gebruik eerst de registratie van RecordingScope via super().__exit__. Geef daarna True uitsluitend voor een LookupError-instantie of subklasse, zoals KeyError en IndexError. Andere fouten lopen ongewijzigd door en normale afloop bewaart drie None-waarden.

Onthoud dit

  • as ontvangt het resultaat van __enter__, niet automatisch de manager.
  • __exit__ verbindt afsluiten aan normale afloop en fouten uit het blok.
  • Een ware retourwaarde onderdrukt de ontvangen fout en vraagt een expliciete afspraak.

Hierna organiseer je zulke functies en klassen in modules en een eigen pakket.

main.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.