Unit 7 • Lesson 9

Raising and Customizing Exceptions

Overview

Sometimes you'll need to trigger your own exceptions to signal errors in logic or input. You'll learn to use raise and define custom exception classes that make your code more robust and predictable, creating clear error communication.

Intermediate 20–25 min

What You Will Learn in This Lesson

By the end of this lesson, you will know:

  • Raising exceptions: Use raise to trigger your own exceptions.
  • When to raise: Understand when it's appropriate to raise exceptions.
  • Custom exceptions: Create your own exception classes for specific errors.
  • Error messages: Provide helpful error messages with your exceptions.
  • Best practices: Write clear, informative exceptions.

Raising Exceptions with raise

Sometimes you want to trigger an exception yourself when something goes wrong. You can do this with the raise keyword.

Basic raise Syntax
raise Exception("Something went wrong!")
Example: Validating Input
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero!")
    return a / b

result = divide(10, 0)
# ValueError: Cannot divide by zero!

When to Raise Exceptions

  • When input is invalid
  • When a required condition isn't met
  • When something unexpected happens
  • To signal errors to calling code

Raising Specific Exceptions

You can raise any built-in exception type:

Raising Different Exception Types
# Raise ValueError for invalid input
if age < 0:
    raise ValueError("Age cannot be negative!")

# Raise FileNotFoundError
if not os.path.exists(filename):
    raise FileNotFoundError(f"File '{filename}' not found!")

# Raise TypeError
if not isinstance(value, int):
    raise TypeError("Expected an integer!")

Creating Custom Exceptions

You can create your own exception classes for specific situations. This makes your code clearer and easier to debug.

Defining a Custom Exception
class InvalidAgeError(Exception):
    """Raised when age is invalid"""
    pass

# Using the custom exception
def set_age(age):
    if age < 0 or age > 150:
        raise InvalidAgeError(f"Age {age} is not valid!")
    return age

Custom Exception Benefits

Custom exceptions make it clear what went wrong and allow callers to catch specific error types.

Custom Exception with Message
class FileFormatError(Exception):
    """Raised when file format is incorrect"""
    def __init__(self, message, filename):
        self.message = message
        self.filename = filename
        super().__init__(self.message)

# Using it
raise FileFormatError("Invalid format", "data.txt")

Raising Exceptions in Functions

Functions often raise exceptions to signal errors to the caller:

Function with Validation
def read_config(filename):
    if not filename.endswith('.txt'):
        raise ValueError("Config file must be .txt format")
    
    try:
        with open(filename, 'r') as file:
            return file.read()
    except FileNotFoundError:
        raise FileNotFoundError(f"Config file '{filename}' not found!")

# Using the function
try:
    config = read_config("settings.txt")
except ValueError as e:
    print(f"Invalid input: {e}")
except FileNotFoundError as e:
    print(f"File error: {e}")

Re-raising Exceptions

Sometimes you want to catch an exception, do something, and then re-raise it:

Re-raising Exceptions
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("Logging: File not found")
    raise  # Re-raise the exception

Use case: You might want to log an error before letting it propagate to the caller.

Practice: Raising Exceptions

Try It Yourself

Try creating and raising your own exceptions:

Click "Run Code" to see your output here

What happened? The function validated the age and raised a ValueError with a helpful message when the age was invalid.

Advanced Custom Exception Examples

Here are more examples of custom exceptions for different scenarios:

Custom Exception with Additional Data
class ValidationError(Exception):
    """Raised when validation fails"""
    def __init__(self, message, field_name, value):
        self.message = message
        self.field_name = field_name
        self.value = value
        super().__init__(self.message)
    
    def __str__(self):
        return f"{self.message} (Field: {self.field_name}, Value: {self.value})"

# Usage
def validate_email(email):
    if '@' not in email:
        raise ValidationError("Invalid email format", "email", email)
    return email

try:
    validate_email("notanemail")
except ValidationError as e:
    print(e)  # Prints: Invalid email format (Field: email, Value: notanemail)
Exception Hierarchy
class FileError(Exception):
    """Base exception for file-related errors"""
    pass

class FileNotFoundError(FileError):
    """Raised when file doesn't exist"""
    pass

class FilePermissionError(FileError):
    """Raised when file permission is denied"""
    pass

class FileFormatError(FileError):
    """Raised when file format is invalid"""
    pass

# You can catch the base exception to catch all file errors
try:
    # Some file operation
    pass
except FileError as e:
    print(f"File error occurred: {e}")

Exception Inheritance

Creating exception hierarchies allows you to:

  • Catch related exceptions together using the base class
  • Organize exceptions logically
  • Provide more specific error handling
  • Make code more maintainable

Best Practices

When raising exceptions, follow these guidelines:

1

Use Appropriate Types

Raise the most specific exception type that fits the error. Use built-in exceptions when they fit, create custom ones when needed.

2

Clear Messages

Provide helpful error messages that explain what went wrong and how to fix it. Include relevant context like filenames or values.

3

Document Exceptions

Document what exceptions your functions might raise in docstrings. This helps other developers (and yourself) understand error handling.

4

Don't Overuse Exceptions

Exceptions are for exceptional cases. Don't use them for normal control flow. Use return values or status codes for expected conditions.

Well-Documented Function
def process_config_file(filename):
    """
    Load and process a configuration file.
    
    Args:
        filename: Path to the configuration file
        
    Returns:
        dict: Configuration dictionary
        
    Raises:
        FileNotFoundError: If the config file doesn't exist
        ValueError: If the file format is invalid
        PermissionError: If file cannot be read
        
    Example:
        >>> config = process_config_file("settings.json")
        >>> print(config['host'])
    """
    # Implementation here
    pass

Summary

In this lesson, you learned:

  • raise: Trigger exceptions yourself when errors occur
  • Specific exceptions: Raise appropriate exception types
  • Custom exceptions: Create your own exception classes for clarity
  • Re-raising: Catch, handle, and re-raise exceptions when needed
  • Best practices: Use clear messages and appropriate types

Remember

Raising exceptions is a way to communicate errors. Make sure your error messages are clear and helpful!

End-of-Lesson Exercises

Think about these questions to reinforce what you've learned:

Exercise 1: When to Raise

When should you raise an exception in your code? Give an example of a function that would benefit from raising exceptions.

Exercise 2: Custom Exceptions

Why might you want to create a custom exception class instead of using a built-in one?