Skip to content
Lesson 11 of 12

Step 1 of 7 · Reading · ~4 min

Learn

Error Handling

Raising Exceptions

By the end you can make your functions refuse bad input with the right exception, group your errors into a family a caller can catch at once, and keep the original error attached when you translate it.

Try and Except was the catching side. Here is the other side: a function that cannot do its job says so with raise instead of returning a sentinel such as -1 or None.

Choosing the class and the message

raise ExceptionClass("message") stops the function and unwinds to the nearest matching except; the example domain throughout is clock times. Pick the built-in class by meaning: ValueError for the right type with an unacceptable value, TypeError for the wrong type, KeyError for a missing key, RuntimeError when nothing fits. Put the bad value in the message; whoever reads it cannot see your variables.

python

A family of your own

class ScheduleError(Exception): pass declares a new exception type. Use it as a recipe for now; what a class is belongs to Advanced Python. The shape is one base class, then one subclass per failure. A caller who only wants to know "the time was bad" catches ScheduleError; one who needs the difference catches BadFormat or OutOfRange. A built-in like TypeError stays outside the family, so it is not swallowed by accident:

python
07:30 -> 450
BadFormat | no colon in '0730' | cause: NoneType
BadFormat | not numbers: '7h:30' | cause: ValueError
OutOfRange | outside the day: '25:10' | cause: NoneType
TypeError | time must be text, got int

Translating with from

Inside an except block, raise NewError(...) from e raises NewError and stores e in its __cause__. The caller sees the family's error and the original ValueError stays attached: 7h:30 above shows it, while 0730 was raised directly, so its __cause__ is None. Without from, a raise inside except records the old error only as __context__, and __cause__ stays None. from None hides the origin on purpose.

A bare raise inside except re-raises the same exception unchanged: print or log a line, then raise, and the caller still gets OutOfRange to handle.

assert versus raise

assert condition, "message" raises AssertionError when the condition is false. It checks your own assumptions, such as a helper that callers are supposed to call with a non-empty list. Never use it for input from a user or a file: running Python with -O removes every assert. Bad input gets raise.

finally and where to catch

A finally block runs whether the body returned or raised, so it holds cleanup that must not be skipped, such as closing a file. Raise where the problem is detected; catch in the one place that can act on it (report, skip, stop). A low-level function that catches and returns -1 pushes the damage downstream: minutes_since_midnight("99:00") giving -1, plus 5, quietly becomes 4.

Common mistakes

  • Raising inside except without from e: e.__cause__ is None, so a report keyed on the cause silently loses a line.
  • Catching Exception when you mean your family: a typo that raises NameError gets reported as bad input and the bug is hidden.
  • Returning a sentinel instead of raising: -1 flows into later arithmetic and gives wrong answers with no error.
  • Using assert for input checks: it vanishes under -O, and AssertionError says nothing about the input.

Your exercise

You write an exception family, a function that raises it, and the one except that reports each bad line. The starter reads the lines and prints the summary. The likeliest mistake is raising the number failure without from e, which loses the caused by line.

Up nextWorking with CSV DataError Handling

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…