IntermediatePython · Lesson 1 of 9

Errors & Exceptions

try / except / else / finally, raising errors and custom exception classes.

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.

errors.pyPython
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__))
Runs in your browser · Python

Key points

  • Catch specific exceptions; never a bare except:.
  • else = success path, finally = always-run cleanup.
  • Custom exceptions + raise ... from err make 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.

parse_lines.pyPython
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
Runs in your browser · Python

Check your understanding

  1. Why should you avoid a bare except:?

  2. When does the else block of a try statement run?

  3. What does raise StudentNotFoundError(id) from err add?

  4. Which exception does int("abc") raise?

Ask AI