Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Antwoorden — Hoofdstuk 7: Foutafhandeling (exceptions)

Opmerking: Code is in het Engels, met korte Nederlandse toelichting erboven of eronder.

Antwoord 1 — safe_int helper

def safe_int(text: str, default: int | None = None) -> int | None:
    try:
        return int(text)
    except ValueError:
        return default

if __name__ == "__main__":
    print(safe_int("123"))        # 123
    print(safe_int("abc"))        # None
    print(safe_int("abc", 0))     # 0

Antwoord 2 — read_float met komma

def read_float(prompt: str) -> float:
    while True:
        raw = input(prompt).strip().replace(",", ".")
        try:
            return float(raw)
        except ValueError:
            print("Please enter a valid number (digits, comma or dot).")
  • Blijft vragen tot het lukt; geen exception lekt naar de aanroeper.

Antwoord 3 — Calculator met ^ en nette fouten

def calc(a: float, op: str, b: float) -> float:
    if op == "+":
        return a + b
    if op == "-":
        return a - b
    if op == "*":
        return a * b
    if op == "/":
        if b == 0:
            raise ZeroDivisionError("division by zero")
        return a / b
    if op == "^":
        return a ** b
    raise ValueError(f"unknown operator: {op}")

if __name__ == "__main__":
    try:
        a = float(input("a: ").strip().replace(",", "."))
        op = input("op (+ - * / ^): ").strip()
        b = float(input("b: ").strip().replace(",", "."))
        print("=", calc(a, op, b))
    except ZeroDivisionError as e:
        print("Error:", e)
    except ValueError as e:
        print("Error:", e)

Antwoord 4 — else/finally en with

# Variant met try/except/else/finally
f = None
try:
    f = open("notes.txt", "r", encoding="utf-8")
    text = f.read()
except FileNotFoundError:
    print("File not found")
else:
    print("Length:", len(text))
finally:
    if f is not None:
        f.close()
    print("Done")

# Equivalent met contextmanager
try:
    with open("notes.txt", "r", encoding="utf-8") as f:
        text = f.read()
    print("Length:", len(text))  # only runs if open+read succeeded
except FileNotFoundError:
    print("File not found")
print("Done")

Antwoord 5 — ConfigError en load_config

from pathlib import Path

class ConfigError(Exception):
    """Raised when configuration is invalid or missing."""


def load_config(path: str) -> dict[str, str]:
    p = Path(path)
    if not p.exists():
        raise ConfigError(f"config file not found: {p}")
    result: dict[str, str] = {}
    try:
        for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), start=1):
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                raise ConfigError(f"invalid line {i}: {line!r}")
            key, value = line.split("=", 1)
            key = key.strip(); value = value.strip()
            if not key:
                raise ConfigError(f"empty key at line {i}")
            if key in result:
                raise ConfigError(f"duplicate key at line {i}: {key}")
            result[key] = value
        return result
    except OSError as e:
        # I/O issues; chain for context
        raise ConfigError(f"failed reading config: {e}") from e

Antwoord 6 — Re‑raise met logging

import logging

logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)


def risky_div(a: float, b: float) -> float:
    return a / b

if __name__ == "__main__":
    try:
        print(risky_div(1, 0))
    except Exception:
        log.exception("Risky division failed")  # logs traceback
        raise  # re-raise so callers still see the error

Antwoord 7 — Exception chaining

def parse_price(text: str) -> int:
    try:
        return int(text)
    except ValueError as e:
        raise ValueError(f"invalid price '{text}'") from e

if __name__ == "__main__":
    print(parse_price("12"))
    print(parse_price("abc"))  # will show chained traceback

Antwoord 8 — Recoverable vs. terminal met teller

import sys

errors = 0
while True:
    raw = input("Amount (empty to stop): ").strip()
    if raw == "":
        break
    try:
        amount = float(raw.replace(",", "."))
        print(f"OK: {amount:.2f}")
        errors = 0  # reset on success
    except ValueError:
        errors += 1
        print("Invalid amount, try again (digits, comma or dot).")
        if errors >= 3:
            print("Too many errors in a row — aborting.")
            sys.exit(1)

Antwoord 9 — Te brede excepties aanscherpen

# BAD: catches everything, hides the real cause, and the try-block is too large
# try:
#     amount = float(input("Amount: "))
#     print(100 / amount)
# except Exception:
#     print("Something went wrong")

# GOOD: small risky parts and specific exceptions
raw = input("Amount: ").strip()
try:
    amount = float(raw)
except ValueError:
    print("Please enter a valid number.")
else:
    try:
        print(100 / amount)
    except ZeroDivisionError:
        print("Division by zero is not allowed.")
  • Specifieke excepties geven betere feedback en je overschaduwt geen onverwachte fouten.
  • Kleine try‑scopes maken het duidelijker welke regel kon falen.

Antwoord 10 — Logging i.p.v. print

import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
log = logging.getLogger("app")

try:
    with open("missing.txt", "r", encoding="utf-8") as f:
        print(f.read())
except FileNotFoundError:
    log.error("File not found: missing.txt")

# logging.exception example (auto-includes traceback)
try:
    1 / 0
except ZeroDivisionError:
    logging.exception("Computation failed")

Uitdaging (optioneel) — Typed config (schets)

class ConfigError(Exception):
    pass

TRUE_SET = {"true", "yes", "1"}
FALSE_SET = {"false", "no", "0"}

def _coerce(value: str):
    v = value.strip()
    low = v.lower()
    if low in TRUE_SET:
        return True
    if low in FALSE_SET:
        return False
    try:
        if "." in v:
            return float(v)
        return int(v)
    except ValueError:
        return v  # leave as string

from pathlib import Path

def load_typed_config(path: str) -> dict[str, object]:
    p = Path(path)
    if not p.exists():
        raise ConfigError(f"config file not found: {p}")
    result: dict[str, object] = {}
    for i, line in enumerate(p.read_text(encoding="utf-8").splitlines(), start=1):
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            raise ConfigError(f"invalid line {i}: {line!r}")
        key, value = line.split("=", 1)
        key = key.strip(); value = value.strip()
        if not key:
            raise ConfigError(f"empty key at line {i}")
        if key in result:
            raise ConfigError(f"duplicate key at line {i}: {key}")
        result[key] = _coerce(value)
    return result