Modules_File_I

1

Error Handling with try-except

▶

When a Python program encounters an error at runtime, it raises an exception — a signal that something unexpected has occurred. Understanding how to catch, inspect, and raise exceptions is essential for writing robust engineering scripts that handle real-world data gracefully.

  • What Is an Exception?
  • Python raises an exception object when an error condition occurs. Rather than silently producing a wrong answer, Python creates an object describing what went wrong and where, giving you the opportunity to respond to it programmatically.
  • An unhandled exception prints a traceback and stops execution immediately. The traceback shows the call stack at the moment of failure, which is useful for debugging but undesirable in production scripts that must keep running.
  • Exceptions are organized in a class hierarchy; catching a parent class also catches its subclasses. For example, catching OSError will also catch FileNotFoundError and PermissionError, since both are subclasses of OSError.
  • Basic try-except Structure
  • Code inside the try block executes normally until an exception is raised. As soon as an exception occurs, Python stops executing the remaining lines in the try block and looks for a matching except clause.
  • If an exception matches the specified type, the except block executes instead of crashing. This lets your program respond to the error — for example, by printing a message, using a default value, or skipping a bad record.
  • Multiple except clauses can handle different exception types separately. Listing specific exception types before more general ones ensures each error kind receives the most appropriate response.
    try:
        value = int(user_input)
    except ValueError:
        print("Input is not a valid integer.")
    except ZeroDivisionError:
        print("Cannot divide by zero.")
    
  • The else and finally Clauses
  • The else block executes when the try block completes without raising an exception. Placing success-path code in else rather than at the end of the try block makes it clear that this code only runs when no error occurred.
  • The finally block always executes, making it ideal for releasing resources. Whether an exception was raised or not, finally runs — ensuring file handles are closed, connections are released, and cleanup always happens.
    try:
        f = open("data.csv")
        data = f.read()
    except FileNotFoundError:
        print("File not found.")
    else:
        print("File read successfully.")
    finally:
        print("Cleanup complete.")
    
  • Both else and finally are optional and can be used together or independently. Use whichever combination matches the logic your script requires — they are not mutually dependent.
  • Accessing Exception Information
  • Use except ExceptionType as e to capture the exception instance. The variable e holds the actual exception object, giving you programmatic access to details about what went wrong.
  • str(e) returns the human-readable error message associated with the exception. Printing or logging str(e) alongside context information makes diagnosing problems far easier than a bare "an error occurred" message.
    try:
        result = 10 / 0
    except ZeroDivisionError as e:
        print(f"Caught an error: {e}")
    
  • The exception object may carry additional attributes relevant to the specific error type. For instance, OSError exposes errno and strerror attributes that provide machine-readable and human-readable descriptions of the operating-system-level error.
  • Raising Exceptions
  • Use raise ExceptionType(message) to raise a specific exception with a description. Raising exceptions deliberately lets you enforce preconditions and communicate clearly to callers when your function receives invalid input.
    def compute_flow(area, velocity):
        if area <= 0:
            raise ValueError(f"Area must be positive, got {area}")
        return area * velocity
    
  • Re-raise a caught exception with a bare raise statement to propagate it after logging. This pattern lets an intermediate layer record diagnostic information without swallowing the exception, preserving the original traceback for higher-level handlers.
  • Custom exception classes can be created by subclassing Exception for domain-specific errors. A class like SensorReadError or InvalidMaterialError makes error handling code more readable and allows callers to catch only errors relevant to their domain.
  • Practical Error Handling in Engineering Scripts
  • Validate inputs at the boundary of your script using try-except before processing begins. Catching bad data — such as non-numeric values in a CSV column — early prevents cascading failures deep inside calculation functions where errors are harder to interpret.
  • Log caught exceptions with enough context to identify the source of the problem. Include the filename, row number, variable name, or sensor ID alongside the error message so that post-run debugging does not require re-running the entire job.
  • Design error handling so the script can continue processing remaining data after a recoverable error. Wrapping the processing of each data record in its own try-except block allows the script to skip bad records and report them at the end, rather than halting on the first failure.
    errors = []
    results = []
    for i, record in enumerate(dataset):
        try:
            results.append(process(record))
        except ValueError as e:
            errors.append(f"Row {i}: {e}")
    
    print(f"Processed {len(results)} records with {len(errors)} errors.")
    
NotesExplains Python's exception handling model and demonstrates how to use try-except blocks to build resilient engineering scripts that recover gracefully from runtime errors. Emphasise to students that good error handling is not about suppressing errors — it is about responding to them intentionally. Encourage the habit of catching specific exception types rather than bare <code>except:</code> clauses, which can accidentally hide bugs.