2.4 · Error handling & exceptions

Level 2 · Data structures & OOP

2.4Error handling & exceptions

Goal: Handle try/except/finally and create your own exceptions.
Estimated time: 12 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

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.

Common misinterpretation: "To be safe, I catch everything with except: and I put pass". No, it is the surest way to see nothing anymore: a bare except catches even the bugs in your own code and pass buries them — the program continues with wrong data, with no signal. Nabil's rule: targeted except (ValueError, FileNotFoundError) + real handling (retry, default, message); what you cannot handle propagates and gets logged.
Errors mastered Frame, handle, signal try (risky) Conversion,file — narrow blockexcept (precise) Targetedtype + real handlingraise/log (signal) Earlyinvariants, unexpected propagates
Frame, handle, signal
Text version of the diagram (SEO / screen readers / translation): 1) Nabil frames the PRECISE RISKY part with try (converting amounts, opening the export) — never the whole program. 2) He catches PRECISELY (except ValueError/FileNotFoundError) with real handling: retry, clear message, else for what follows, finally for cleaning. 3) He SIGNALS early with raise (negative amount rejected from __init__) and lets the unexpected through with its logged traceback — never except: pass. Lesson: fail early and loudly, never silence.

Did you know?

Nabil once wrote "except: pass" to silence an error — and his Algiers report stayed at zero for a week without alerting anyone.
True: silencing an error buries the live bug — catch precisely and handle.

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

  1. Ask for an amount and convert it: wrap float() in a try/except ValueError that retries on non-numeric input.
  2. Handle an average by catching ZeroDivisionError (no sales) with a clear message instead of a crash.
  3. Open the export in try/except FileNotFoundError; add a finally that displays "import done" in all cases.
  4. Write a function that raises (raise) ValueError for a negative amount, and test it with try/except.
You anticipate errors with try/except/finally and raise your own exceptions: your programs become robust against surprises.

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.

Section exit checklist
  • 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.
Self-assessment 1-5: are my try blocks narrow and targeted?do I signal early instead of silencing?

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.

Check your understanding

What must be absolutely avoided in error handling?

Additional resources (optional, to go further)

Level 2 examFresh, harder questions to test what you've learned