CHAPTER 23 · INTERMEDIATE4 min read

Exceptions and Validation

Give failures useful meaning and recover only when your program knows what recovery should do.

Python 3.12+Free to readModule 5 of 13
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Validate values at clear boundaries.
  • Catch narrow exceptions around the operation that can fail.
  • Translate failures while preserving their cause.

01Separate invalid input from unexpected failure

Validation asks whether an incoming value meets a contract. A delivery quantity may need to be an integer between one and one hundred. Validate before changing inventory so a rejected request leaves the old state intact. Functions should say what they accept, what they return, and which failures callers can reasonably handle.

An exception carries a failure out of the current operation until a handler matches it. Use ValueError when the type is appropriate but its value is invalid, and TypeError when an unsupported type violates your API. For user-entered text, conversion and range checking are separate steps: "many" is not parseable, while "-2" parses but violates the business rule.

quantity.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Keep the try block narrow

Put only the operation you intend to recover from inside try. If a large block catches KeyError, an unrelated programming mistake inside a helper can be misreported as a missing configuration key. Narrow blocks preserve the connection between the failure and the recovery action.

Use else for work that should happen only after the protected operation succeeds, and finally for cleanup that must run whether it succeeds or fails. A context manager often expresses resource cleanup more clearly. Do not return from finally: it can replace a pending return or suppress an exception, hiding the original result.

optional_setting.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Recover at the right layer

A low-level loader usually cannot decide whether a missing file means "create a new workspace" or "stop because a required backup disappeared". Let the caller that knows the workflow choose. Catching an error merely to print it and continue often converts a visible failure into an incorrect success.

At a command-line boundary, catching an expected validation exception and showing a concise message is useful. Inside the calculation, raise it. When translating a filesystem or parser failure into your domain vocabulary, use raise NewError(...) from exc so debugging still reveals the cause. Define a custom exception when callers need to distinguish your failure from other ValueErrors.

domain_error.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Do not use exceptions to hide uncertainty

A bare except also catches interruption signals such as KeyboardInterrupt. Catch Exception only at a deliberate outer boundary where you will log and fail safely, not as a shortcut around understanding likely failures. Never use pass as an unexplained universal recovery strategy.

Assertions describe internal assumptions during development; they are not a substitute for validating external input because optimized execution can remove assert statements. Write explicit checks and raises for required runtime rules. During testing, cover a normal input, a boundary, and each important rejection path so your error contract is as intentional as your success contract.

LET IT CLICK

The ideas worth keeping.

  • Validate before committing state changes.
  • Catch specific failures in the smallest useful try block.
  • Recover with context and preserve the cause when translating an error.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Reject a bad percentage

Write parse_percent(text) accepting integer text from 0 through 100 inclusive. Produce useful ValueError messages for malformed and out-of-range input.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
def parse_percent(text):    try:        value = int(text)    except ValueError as exc:        raise ValueError("percentage must be an integer") from exc    if not 0 <= value <= 100:        raise ValueError("percentage must be in 0..100")    return valueassert parse_percent("0") == 0assert parse_percent("100") == 100for text in ("101", "-1", "half"):    try:        parse_percent(text)    except ValueError as exc:        print(exc)

Both endpoints are valid. Parsing errors retain their original cause; range errors report the rule that failed.

02

Avoid a misleading default

Read attempts from a dictionary. Use 1 when the key is missing, but allow malformed present text to raise ValueError instead of silently defaulting.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
def get_attempts(settings):    try:        raw = settings["attempts"]    except KeyError:        return 1    return int(raw)assert get_attempts({}) == 1assert get_attempts({"attempts": "4"}) == 4try:    get_attempts({"attempts": "many"})except ValueError:    print("Bad configuration is visible")

Missing and malformed are different states. Only the key lookup is protected by the missing-key handler.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01Where should you catch a missing database-file error if only the CLI knows whether creating a new workspace is allowed?
02What does raise ImportFailure("bad row") from exc add?
03Why is assert quantity > 0 unsuitable as the only validation of user input?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Reject a bad percentage

Write parse_percent(text) accepting integer text from 0 through 100 inclusive. Produce useful ValueError messages for malformed and out-of-range input.

EXPECTED OUTPUT
exc
challenge.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this chapter, when you want a second angle or the full details.

One chapter closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock chapter completion.