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.
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:
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
exceptwithoutfrom e:e.__cause__isNone, so a report keyed on the cause silently loses a line. - Catching
Exceptionwhen you mean your family: a typo that raisesNameErrorgets reported as bad input and the bug is hidden. - Returning a sentinel instead of raising:
-1flows into later arithmetic and gives wrong answers with no error. - Using
assertfor input checks: it vanishes under-O, andAssertionErrorsays 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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…