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