Skip to content

7.F Fail: Error Control

Julio Toboso edited this page Feb 24, 2026 · 1 revision

Raising Exceptions in Python ๐Ÿšจ

One of the most powerful tools you have as a Python developer is the ability to stop execution when something goes wrong โ€” intentionally.

Instead of letting bugs propagate silently, you can explicitly signal that a condition is invalid. This is done by raising an exception.


๐Ÿ”ฐ Core Idea

An exception is a runtime signal that something unexpected or invalid has occurred.

Python allows you to trigger this signal yourself using the raise keyword.

Think of it as:

โ€œThis situation should not happen โ€” abort and report.โ€


โš™๏ธ Basic Syntax

raise Exception("Error message")

You specify:

  1. The type of exception
  2. The explanatory message

๐Ÿงช Example 1 โ€” Guarding Against Invalid Values

Suppose negative numbers are not allowed:

x = -1

if x < 0:
    raise Exception("Sorry, no numbers below zero")

What happens:

  1. Python evaluates the condition x < 0
  2. The condition is True
  3. The program stops immediately
  4. The message is displayed

This pattern is called a guard clause โ€” a defensive programming technique.


โš ๏ธ Why Raise Exceptions?

Without exceptions:

  • Errors may propagate silently
  • Debugging becomes difficult
  • Incorrect data contaminates later computations

With exceptions:

  • Failures occur early
  • Causes are easier to identify
  • Code becomes safer and more predictable

This aligns with a key engineering principle:

Fail fast, fail loudly.


๐ŸŽฏ Choosing the Right Exception Type

Python provides many built-in exception classes.

You should use the most appropriate one to communicate intent.

Example: enforcing integer input.

x = "hello"

if not type(x) is int:
    raise TypeError("Only integers are allowed")

Here we use:

  • TypeError โ†’ wrong data type

This is more informative than a generic Exception.


๐Ÿง  Conceptual Model

You can think of raise as creating an error signal in the program flow:

Normal execution โ†’ Condition detected โ†’ raise โ†’ Program interrupted

This mechanism integrates with:

  • try / except blocks
  • Error propagation across functions
  • Debugging tools

๐Ÿ’ก Best Practices

Prefer specific exceptions

Good:

raise ValueError("Age must be positive")

Less good:

raise Exception("Something went wrong")

Specific errors improve readability and maintainability.


Validate early

Check inputs at the boundaries of your functions.

def sqrt(x):
    if x < 0:
        raise ValueError("Cannot compute square root of negative number")

Write meaningful messages

Error messages should explain:

  • What went wrong
  • Why it is invalid

๐Ÿช„ Advanced Insight โ€” Exceptions as Contracts

Exceptions are closely related to design by contract.

A function implicitly states:

โ€œIf you give me valid input, I guarantee correct output.
Otherwise, I will raise an exception.โ€

This turns runtime checks into formal guarantees.


โœ… Summary

  • raise allows you to trigger exceptions manually
  • Exceptions stop execution and report errors
  • Use specific exception types when possible
  • Raising exceptions is essential for robust, safe programs

๐Ÿงฉ Creating Custom Exceptions (Your Own Error Types)

Built-in exceptions are useful, but sometimes your program has domain-specific rules that deserve their own error language.

For example:

  • A banking app โ†’ InsufficientFundsError
  • A game engine โ†’ InvalidMoveError
  • A scientific simulation โ†’ ConvergenceError

When you define custom exceptions, you are giving your code a vocabulary for failure.

This dramatically improves:

  • Readability
  • Debugging clarity
  • Architectural design

๐Ÿ”ฐ Core Idea

A custom exception is simply a class that inherits from Exception (or another exception type).

class MyError(Exception):
    pass

Thatโ€™s it.

You now have a brand-new error type.


โš™๏ธ Basic Example

Suppose we want to forbid negative ages.

class NegativeAgeError(Exception):
    pass


age = -5

if age < 0:
    raise NegativeAgeError("Age cannot be negative")

Now the error communicates meaning, not just failure.


๐ŸŽฏ Why Custom Errors Matter

Compare these two messages:

Generic:

ValueError: Invalid input

Specific:

NegativeAgeError: Age cannot be negative

The second one tells you immediately:

  • What failed
  • Why it failed
  • Where to look

This reduces debugging time dramatically.


๐Ÿง  Conceptual Model โ€” Errors as Types

In strongly-typed thinking (similar to engineering disciplines or Ada-style design), exceptions are part of your type system of behavior.

Your program does not only define:

  • Data types
  • Functions

It also defines:

  • Failure types

So the system becomes:

Valid State  โ†’ Normal execution
Invalid State โ†’ Specific exception type

This is a powerful abstraction.


๐Ÿงช Adding Information to Custom Errors

Custom exceptions can carry data.

class TemperatureTooHighError(Exception):

    def __init__(error, temperature):
        message = f"Temperature {temperature}ยฐC exceeds safe limit"
        super().__init__(message)
        error.temperature = temperature

Usage:

temp = 120

if temp > 100:
    raise TemperatureTooHighError(temp)

Now the exception contains structured information.

You can access it later:

except TemperatureTooHighError as e:
    print(e.temperature)

โš ๏ธ Inheriting from Specific Exception Types

You donโ€™t always need to inherit directly from Exception.

You can specialize existing categories.

Example:

class InvalidEmailError(ValueError):
    pass

This communicates:

โ€œThis is a ValueError โ€” but more specific.โ€

This helps large systems where different error families exist.


๐Ÿช„ Designing an Error Hierarchy

In complex projects, you can build error trees.

class AppError(Exception):
    pass


class DatabaseError(AppError):
    pass


class ConnectionError(DatabaseError):
    pass

Benefits:

  • Catch broad categories when needed
  • Catch specific errors when necessary

Example:

try:
    connect_to_db()
except DatabaseError:
    print("Database problem detected")

๐Ÿ’ก Best Practices

1. Name errors with โ€œErrorโ€ suffix

InvalidStateError
ConfigurationError
PermissionDeniedError

Consistency improves clarity.


2. Keep them lightweight

Most custom exceptions only need:

class MyError(Exception):
    pass

Add complexity only when useful.


3. Use them to express contracts

Instead of vague checks:

if not valid:
    raise Exception("Bad input")

Prefer:

if not valid:
    raise InvalidConfigurationError("Missing API key")

๐Ÿงฌ Advanced Insight โ€” Exceptions as Architecture

Custom exceptions help define system boundaries.

Each module can expose:

  • Public functions
  • Public error types

This creates a clean API:

Module API =
    Functions
    Exceptions

Professional libraries always do this.


โœ… Summary

  • Custom exceptions are classes that inherit from Exception
  • They give semantic meaning to failures
  • They improve debugging and architecture
  • They can store additional data
  • Large systems often define error hierarchies

Custom errors turn bugs into structured information instead of chaos.


Clone this wiki locally