Wenn die Außenwelt Probleme macht · Übung
Dort abfangen, wo der Fehler eine Bedeutung hat
Der inklusive Filter ist korrigiert. Der Befehl hat noch eine zu weit gefasste Grenze aus Kapitel 8:
try:
document = convert_catalog(input_path, output_path, minimum_quantity)
except (FileNotFoundError, UnicodeDecodeError, csv.Error, ValueError) as exc:
...
Dieses Tupel nennt nur erwartete Typen, doch der try-Block umfasst Lesen, Auswählen, Validieren und Schreiben. Der Ort ist genauso wichtig wie die Klasse. Ein ValueError beim Einlesen einer beschädigten CSV-Zeile bedeutet eine erwartete ungültige externe Eingabe. Ein ValueError aus der Validierung der Schreibfunktion bedeutet, dass Projektcode seiner eigenen Schreibfunktion ein ungültiges internes Dokument übergeben hat. Beide gleich zu behandeln würde diesen Unterschied auslöschen.
Eine Grenze um eine verstandene Operation ziehen
Erstelle run_catalog(input_path, output_path, minimum_quantity). Seine erste Grenze umschließt nur das Lesen:
try:
items = read_catalog_csv(input_path)
except (OSError, UnicodeDecodeError, csv.Error, ValueError) as exc:
print(f"Could not read {input_path}: {exc}", file=sys.stderr)
return 1
OSError ist die Familie der Betriebssystemfehler, zu der fehlende Pfade, Berechtigungsfehler und andere fehlgeschlagene Dateioperationen gehören. Hier hat sie eine Bedeutung: Die angeforderte Eingabe konnte nicht geöffnet oder gelesen werden. UnicodeDecodeError, csv.Error und ValueError bedeuten, dass aus den Bytes, der CSV-Syntax oder dem festgelegten Schema kein Katalog werden konnte.
Die Meldung enthält den genauen Eingabepfad und die begrenzte Ursache. stdout bleibt leer. Besonders wichtig: Die Schreibfunktion ist noch nicht gelaufen, deshalb bleibt eine bereits vorhandene Ausgabedatei Byte für Byte unverändert.
Führe die Auswahl nach dieser Fehlerbehandlung außerhalb jedes try aus:
selected = select_items(items, minimum_quantity)
Wenn die Auswahl KeyError, TypeError, ValueError, RuntimeError oder sogar OSError auslöst, gelangt die ursprüngliche Ausnahme nach außen. Die Auswahl greift nicht auf die Außenwelt zu. Ihren Fehler als Lese- oder Schreibfehler zu bezeichnen wäre daher falsch.
Der Ausgabeoperation eine eigene Grenze geben
Umschließe nur die Schreibfunktion:
try:
document = write_catalog_json(selected, output_path)
except OSError as exc:
print(f"Could not write {output_path}: {exc}", file=sys.stderr)
return 1
Hier wird nur ein Schreibfehler des Betriebssystems erwartet. Ein ValueError oder TypeError aus der Validierung verletzt eine interne Invariante und muss unverändert nach außen gelangen.
Gib die Erfolgszeile erst aus, nachdem write_catalog_json zurückgekehrt ist. „Wrote N items“ ist eine Aussage über einen abgeschlossenen Vorgang. Eine frühere Ausgabe wäre eine als Erfolg getarnte Falschaussage.
Die tatsächliche Grenze für die Ergebnisdatei
Dieses Projekt schreibt direkt an den angeforderten Pfad. Scheitert das Betriebssystem vor dem Öffnen, kann eine alte Datei unverändert bleiben. Passiert der Fehler nach dem Leeren oder nachdem einige Bytes geschrieben wurden, kann die Datei fehlen oder unvollständig sein. Dieses Kapitel verspricht keine Rückabwicklung, die es nicht implementiert hat.
Es gibt auch keinen automatischen Wiederholungsversuch. Einen fehlenden Pfad oder einen Berechtigungsfehler ohne Änderung erneut zu versuchen wiederholt nur denselben Fall. Die Diagnose liefert dem Aufrufer Hinweise; sie erfindet keine Wiederherstellungsstrategie.
Aktualisiere main(argv=None) so, dass argparse weiterhin für Hilfe und Grammatikfehler zuständig ist. Gib dann seine drei eingelesenen Werte an run_catalog weiter. Der vollständige Vertrag lautet:
| Situation | stdout | stderr | Status |
|---|---|---|---|
| Erfolg | Wrote N items to PATH. | leer | 0 |
| Lese-/Eingabefehler | leer | Could not read INPUT: cause | 1 |
OSError bei der Ausgabe | leer | Could not write OUTPUT: cause | 1 |
| Parserfehler | leer | Aufrufhinweis und Fehler von argparse | 2 |
| unerwarteter Defekt | keine Zusage | ursprünglicher Traceback | ungleich null |
Implementiere die beiden Grenzen, die jeweils eine Operation umfassen.
Ob du es richtig gemacht hast, zeigt sich nicht daran, dass das Programm nicht mehr abstürzt. Entscheidend ist, ob dieselbe Ausnahmeklasse je nach Herkunft unterschiedlich behandelt wird. Ein ValueError aus der Lesefunktion sollte eine kurze Meldung erzeugen; ein ValueError aus der eigenen Validierung der Schreibfunktion weiterhin einen Traceback. Wenn ein breites try beide abdeckt, kannst du sie nicht unterscheiden. Die Person, die dein Programm ausführt, kann es dann ebenfalls nicht.
Ein ValueError erreicht deinen Code. Seine Herkunft entscheidet, was zu tun ist. Warum?
Warum läuft select_items außerhalb jedes try-Blocks?
Aufgabe
Strukturiere die CLI-Steuerung in run_catalog(input_path, output_path, minimum_quantity) um.
Fange
OSError,UnicodeDecodeError,csv.ErrorundValueErrornur umread_catalog_csvherum ab. Gib dannCould not read INPUT_PATH: CAUSEauf stderr aus und gib1zurück.Führe
select_itemsaußerhalb jedestry-Blocks aus.Fange nur
OSErrorumwrite_catalog_jsonherum ab. Gib dannCould not write OUTPUT_PATH: CAUSEauf stderr aus und gib1zurück.Gib nach erfolgreichem Schreiben
Wrote N items to OUTPUT_PATH.auf stdout aus und gib0zurück.Lass
main(argv=None)Argumente einlesen undrun_catalog(...)zurückgeben.
Behalte bei argparse Hilfe mit Status 0 und Grammatikfehler mit Status 2 bei. Lass Auswahlfehler und Validierungsfehler der Schreibfunktion unverändert nach außen gelangen. Ergänze weder Wiederholungsversuche noch Zusagen über Rückabwicklung.