2.4Error handling & exceptions
The essentials in 30 seconds
- Frame the precise risky part: try on conversion, file, network — never the whole program.
- Catch narrow, not wide: targeted except ValueError/FileNotFoundError — bare except or pass buries bugs.
- Signal early and loudly: raise for your invariants, else/finally where appropriate, log then degrade cleanly.
Running mini-case — Nabil hardens the month's import
Final act: Nabil's monthly import crashes at the first surprise — missing export, non-numeric amount, total divided by zero when there is no sale. Users see a red traceback. He hardens it: amount conversion in try/except ValueError with retry, opening in try/except FileNotFoundError with a clear message, division guarded against ZeroDivisionError, negative amounts rejected by raise from Vente creation (2.3). The rest propagates with its traceback: nothing is silenced. The level-2 import is delivered: data, text, objects, errors mastered.
Epilogue of the 2.1-2.3 running thread: Nabil's import no longer crashes at the first surprise.
1. The exception is not a disaster
ValueError, KeyError, FileNotFoundError, ZeroDivisionError: informative objects, not insults. try/except frames the precise risky part (converting input, opening a file, dividing) — never the whole program: catching wide hides bugs. The traceback reads bottom-up: the error type then the faulty line. Nabil applies it: only the amount conversion is framed, not the whole import.
2. try/except/else/finally: each has its role
try holds the risky part, except ValueError catches a precise type with a useful message, else runs what follows if all goes well, finally always cleans up (close, release — even on error). raise ValueError("...") signals your own invariants: a negative amount is rejected early and loudly, at creation. Nabil thus rejects negative amounts in __init__: the error is signaled at the source, not three functions later.
3. Letting through what you don't handle
Golden rule: only catch what you know how to handle (retry, default value, clear message). The rest propagates with its traceback: silencing it with except: pass buries live bugs — the worst reflex. A bare except masks everything, including what you should see. In production, log then degrade cleanly (message to the user, controlled stop). Nabil's import retries invalid amounts, reports the missing export — and lets the unexpected propagate.
Did you know?
A silenced bug works against you in silence.
🔎 Concrete example — 🔎 Concrete example — Nabil's robust input, with no silencing
The precise case is caught, the rest would propagate, as at Nabil's:
def lire_montant(texte):
try:
montant = float(texte)
except ValueError:
raise ValueError(f"invalid amount: {texte!r}") from None
if montant < 0:
raise ValueError("negative amount forbidden")
return montant
try:
print(lire_montant("1250.5"))
except ValueError as e:
print(f"Error: {e}")
Reading: targeted ValueError, invariant signaled by raise, useful message.
Lesson: never except: pass — it buries live bugs.
Section vocabulary
- Exception
- Error occurring at runtime (ZeroDivisionError, ValueError, FileNotFoundError…) that stops the program if unhandled. Read bottom-up in the traceback.
Simple example: Nabil's missing export raises FileNotFoundError.
What it is not: a syntax error that prevents startup.
Reflex: runtime crash = exception to read.
- try / except
- try holds the risky code (narrow block); except catches a PRECISE TYPE and runs real handling instead of crashing.
Simple example: Nabil wraps float(montant) in an except ValueError.
What it is not: a bare except that catches everything without handling.
Reflex: precise risky part = try + targeted except.
- else / finally
- else: what follows if no error; finally: run IN ALL CASES, ideal for releasing a resource (closing a file).
Simple example: Nabil displays "import done" in finally.
What it is not: cleanup that only runs on success.
Reflex: guaranteed cleanup = finally.
- raise
- Deliberately raises an exception to signal an abnormal situation (violated invariant): fail early and loudly, at the source.
Simple example: Nabil's Vente rejects negative amounts by raise.
What it is not: a silent return that lets wrong data through.
Reflex: violated invariant = immediate raise.
- Traceback
- The error trace displayed by Python: read bottom-up, it shows the error type and the faulty line. Log it, don't mask it.
Simple example: Nabil reads the bottom of the traceback to find the faulty conversion.
What it is not: a message masked by except: pass.
Reflex: traceback = read the bottom, fix the line.
In practice — In practice — Making a program reliable with try/except
- Ask for an amount and convert it: wrap float() in a try/except ValueError that retries on non-numeric input.
- Handle an average by catching ZeroDivisionError (no sales) with a clear message instead of a crash.
- Open the export in try/except FileNotFoundError; add a finally that displays "import done" in all cases.
- Write a function that raises (raise) ValueError for a negative amount, and test it with try/except.
Script ready to say — justifying targeting in code review (30 s)
"For Nabil's import, each try is narrow: conversion in ValueError, export in FileNotFoundError, with real handling behind each. Negative amounts go through raise at creation. And no except pass: what we cannot handle propagates and gets logged — we never bury a bug."
Say it while showing a bare except replaced by two targeted excepts.
Copy-paste template — hardened end-to-end import
import json
from pathlib import Path
def lire_montant(texte):
montant = float(texte)
if montant < 0:
raise ValueError("negative amount forbidden")
return montant
fichier = Path("ventes.json")
try:
lignes = json.loads(fichier.read_text(encoding="utf-8"))
except FileNotFoundError:
print("Export not found: check ventes.json")
lignes = []
try:
montants = [lire_montant(str(l)) for l in lignes]
except ValueError as e:
print(f"Skipped invalid line: {e}")
montants = []
else:
print(f"{len(montants)} amounts read")
finally:
print("import done")
Input to retry
The amount typed by Nabil goes through targeted float(): ValueError retries, the rest propagates.
Missing export
The month's file is missing: FileNotFoundError shows a clear message instead of a red traceback.
Average with no sales
No sales in Algiers that day: ZeroDivisionError caught, "no average" displayed.
Forbidden amount
A negative amount triggers raise from Vente: fail early, signal at the source.
✓ Best practices
- Frame the precise risky part with narrow try blocks.
- Catch precise types with real handling (else/finally where appropriate).
- Raise with raise for invariants; log then degrade cleanly.
✗ Common pitfalls
- Using a bare except: or except: pass (masks and buries bugs).
- Framing the whole program in a single try.
- Silencing instead of handling (continuing with wrong data).
Key takeaways
- try/except catches errors instead of letting the program crash — narrow block on the risky part.
- Target the error type (ValueError, FileNotFoundError…) rather than a bare except.
- else = what follows if OK; finally always runs: perfect for releasing a resource.
- raise raises an exception to signal an abnormal situation; SyntaxError is not handled with try.
48h challenge — apply tomorrow
Harden a real script (Nabil's or yours): narrow try + targeted except on each fragile point (conversion, file, division), one raise for an invariant, a real finally — and hunt down every except: pass.
- I can explain try/except/else/finally and the role of each.
- I can catch precisely and raise with raise for my invariants.
- I have removed every bare or silenced except from my code.
Frequently asked questions
Should I catch all errors with a general except?
No: a bare "except:" masks everything, including bugs you should see. Catch targeted exceptions (except ValueError) to handle only what you anticipate, and let the rest propagate. It is a golden rule of reliability.
What is the difference between a syntax error and an exception?
A syntax error (SyntaxError) prevents the program from starting: the code is badly written. An exception occurs while running otherwise valid code (missing file, division by zero). Only exceptions are handled with try/except.
What must be absolutely avoided in error handling?