When something goes wrong, Python raises an exception: ValueError for bad values, KeyError for missing dictionary keys, FileNotFoundError, and so on. Unhandled, it stops the program with a traceback.
Catch only the exceptions you can actually handle, as specifically as possible — never use a bare except: that hides every bug. else runs when no exception happened; finally always runs (for cleanup).
Raise your own errors with raise. Custom exception classes (subclasses of Exception) let callers tell different failures apart. Use raise ... from err to keep the original cause.
class StudentNotFoundError(Exception):
def __init__(self, student_id: str) -> None:
super().__init__(f"Student {student_id} not found")
self.student_id = student_id
STUDENTS = {"s1": "Amina"}
def get_student(student_id: str) -> str:
try:
return STUDENTS[student_id]
except KeyError as err:
raise StudentNotFoundError(student_id) from err
def parse_score(text: str) -> int:
score = int(text) # may raise ValueError
if not 0 <= score <= 100:
raise ValueError(f"score must be 0-100, got {score}")
return score
for raw in ["88", "abc", "150"]:
try:
score = parse_score(raw)
except ValueError as err:
print(f"{raw!r}: invalid ({err})")
else:
print(f"{raw!r}: ok -> {score}")
finally:
print(" checked", raw)
try:
get_student("s9")
except StudentNotFoundError as err:
print("404:", err, "| cause:", repr(err.__cause__))Key points
- Catch specific exceptions; never a bare
except:. else= success path,finally= always-run cleanup.- Custom exceptions +
raise ... from errmake failures clear and traceable.
Exercise
Write parse_line(line) that turns "Amina,88" into (name, score) and raises a custom InvalidLineError with a helpful message for a missing comma, empty name or bad score. Process a list of lines and print the valid rows plus an error count.
Show solution
Try the exercise yourself first — then compare your approach with this one.
parse_line raises one custom exception type with a specific message for each problem. The loop catches only InvalidLineError, so a genuine bug elsewhere would still surface instead of being hidden.
class InvalidLineError(Exception):
"""Raised when a 'name,score' line cannot be parsed."""
def parse_line(line: str) -> tuple[str, int]:
if "," not in line:
raise InvalidLineError(f"missing comma in {line!r}")
name, _, score_text = line.partition(",")
name = name.strip()
if not name:
raise InvalidLineError(f"empty name in {line!r}")
try:
score = int(score_text)
except ValueError as err:
raise InvalidLineError(f"score is not a whole number in {line!r}") from err
if not 0 <= score <= 100:
raise InvalidLineError(f"score out of range in {line!r}")
return name, score
lines = ["Amina,88", "Juma 42", ",70", "Neema,abc", "Ali,150", "Rehema, 71"]
valid, errors = [], 0
for line in lines:
try:
valid.append(parse_line(line))
except InvalidLineError as err:
errors += 1
print("Skipped:", err)
print(valid) # [('Amina', 88), ('Rehema', 71)]
print("Errors:", errors) # Errors: 4