Error Handling and Exception Handling

Understanding Exceptions in Python

In Python, exceptions are raised when an error occurs during program execution. They act as signals to the program that something is wrong, allowing it to handle errors gracefully. Exceptions are categorized into two types: built-in exceptions (predefined) and custom exceptions (created by developers).

Built-in Exceptions

Python provides a predefined set of exceptions. Common examples include:

  • ValueError: Raised when a function is given invalid data.
  • TypeError: Occurs when two different data types are used where a conversion is required.
  • IndexError: When trying to access an index that is out of range.
  • KeyError: When a key is not found in a dictionary.

These exceptions are raised by built-in functions and modules, such as divmod() or input().

Custom Exceptions

Custom exceptions are created using the class keyword. Developers can define their own exceptions to handle specific error scenarios. For example:

class InvalidInputError(Exception):
    def __init__(self, message, code):
        super().__init__(message)
        self.code = code

This class can be raised in a function to indicate invalid input.

Error Handling in Functions

Error handling in Python is achieved using the try-except block. This structure allows a function to catch and handle exceptions that occur during its execution.

try-except Blocks

The try block contains the code that may raise an exception, while the except block catches the exception. Multiple except clauses can handle different exception types.

def divide(a, b):
    try:
        result = a / b
        print("Result:", result)
    except ZeroDivisionError:
        print("Error: Division by zero is not allowed.")
    except ValueError:
        print("Error: Invalid input.")
    finally:
        print("Execution completed.")

In this example, ZeroDivisionError is caught specifically, and ValueError is handled separately. The finally block ensures execution continues regardless of exceptions.

Handling Multiple Exceptions

Multiple except clauses can be used to handle different exception types. For instance:

def process_data(data):
    try:
        # Code that may raise exceptions
        result = data ** 2
    except TypeError:
        print("Error: Non-numeric input.")
    except ValueError:
        print("Error: Invalid numeric input.")

Best Practices for Error Handling

  1. Use Specific Exceptions: Catch specific exceptions rather than general ones to avoid masking errors.
  2. Log Errors: Record errors for debugging and analysis.
  3. Use finally: Ensure code executes regardless of exceptions.
  4. Raise Custom Exceptions: Define custom exceptions for domain-specific errors.
  5. Test Thoroughly: Validate edge cases and ensure error handling is robust.

Debugging and Testing

Use tools like pdb (Python Debugger) or unittest to test error handling. For example:

import unittest

class TestErrorHandling(unittest.TestCase):
    def test_divide(self):
        self.assertEqual(divide(10, 2), 5)
        self.assertEqual(divide(10, 0), "Error: Division by zero is not allowed.")

Conclusion

Error handling is critical for building reliable Python functions. By using try-except blocks, developers can prevent program crashes and improve user experience. Properly catching exceptions, logging errors, and raising custom exceptions ensures code resilience. Following best practices, such as using specific exceptions and testing thoroughly, further enhances the robustness of Python functions.