Error Handling and Exceptions

Errors happen: missing files, bad user input, network timeouts, division by zero. Business analysts need scripts that handle errors gracefully—log the issue, skip bad rows, retry failed requests—instead of crashing. Master error handling and your tools become production-ready.

Estimated reading time: 25–30 minutes

Core Error Handling

  • try / except → catch errors, prevent crashes
  • except Type → handle specific errors differently
  • finally → cleanup code that always runs
  • else → code that runs only if no error

Great for: robust scripts, production tools

Common Exception Types

  • FileNotFoundError → missing file
  • ValueError → invalid conversion (int("abc"))
  • KeyError → missing dict key
  • ZeroDivisionError → divide by zero

Great for: anticipating real-world failures

Try / Except — Catching Errors

Wrap risky code in try blocks. If an error occurs, except blocks handle it instead of crashing.

python
# Without error handling (crashes)
# result = 10 / 0  # ZeroDivisionError

# With error handling (graceful)
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Error: Cannot divide by zero")
    result = None

print("Script continues:", result)

Handling Multiple Exception Types

Different errors need different responses. Use multiple except blocks or tuple syntax.

python
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Error: Division by zero")
        return None
    except TypeError:
        print("Error: Invalid types (need numbers)")
        return None

print(safe_divide(10, 2))    # 5.0
print(safe_divide(10, 0))    # Error: Division by zero, None
print(safe_divide(10, "x"))  # Error: Invalid types, None

# Tuple syntax for same handling
try:
    value = int(input("Enter number: "))
except (ValueError, TypeError):
    print("Invalid input")
    value = 0

Accessing Error Details

Use as e to capture the error object for logging or debugging.

python
try:
    data = {"name": "Ana"}
    print(data["age"])  # KeyError
except KeyError as e:
    print("Missing key:", e)
    print("Available keys:", list(data.keys()))

Finally — Cleanup Code

finally blocks always run, even if an error occurs. Perfect for closing files or connections.

python
def read_file(filename):
    f = None
    try:
        f = open(filename, "r")
        return f.read()
    except FileNotFoundError:
        print("Error:", filename, "not found")
        return None
    finally:
        if f:
            f.close()
            print("File closed")

content = read_file("data.txt")

Else — Success-Only Code

else blocks run only if no exception occurred. Useful for separating success logic from error handling.

python
try:
    value = int(input("Enter number: "))
except ValueError:
    print("Invalid input")
else:
    print("You entered:", value)
    # Process valid input here
finally:
    print("Input validation complete")

Raising Exceptions

Use raise to trigger errors when business rules are violated.

python
def process_order(qty, price):
    if qty < 0:
        raise ValueError("Quantity cannot be negative")
    if price < 0:
        raise ValueError("Price cannot be negative")
    return qty * price

try:
    total = process_order(-5, 10)
except ValueError as e:
    print("Order validation failed:", e)

Custom Exceptions

Create domain-specific exceptions for clearer error handling.

python
class InvalidSKUError(Exception):
    pass

def validate_sku(sku):
    if not sku.startswith("SKU"):
        raise InvalidSKUError("Invalid SKU format: " + sku)
    return True

try:
    validate_sku("ABC123")
except InvalidSKUError as e:
    print("Validation error:", e)

Cornerstone Project — Robust CSV Processor (step-by-step)

Build a CSV processor that handles missing files, bad data, invalid rows, and logs all errors. This is how production ETL scripts work—they do not crash on bad data, they skip it and report what went wrong.

Step 1 — Create sample CSV with bad data

Simulate real-world messiness: missing values, invalid types, extra columns.

python
import csv

# Create test data with issues
test_data = [
    {"sku": "SKU001", "qty": "10", "price": "12.99"},
    {"sku": "SKU002", "qty": "abc", "price": "24.50"},  # bad qty
    {"sku": "INVALID", "qty": "5", "price": "8.75"},    # bad SKU
    {"sku": "SKU003", "qty": "20", "price": ""},        # missing price
]

with open("orders.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["sku", "qty", "price"])
    writer.writeheader()
    writer.writerows(test_data)

Step 2 — Define validation functions

Each function raises specific errors for different validation failures.

python
class ValidationError(Exception):
    pass

def validate_sku(sku):
    if not sku.startswith("SKU"):
        raise ValidationError("Invalid SKU format: " + sku)
    return sku

def validate_qty(qty_str):
    try:
        qty = int(qty_str)
        if qty <= 0:
            raise ValidationError("Quantity must be positive: " + str(qty))
        return qty
    except ValueError:
        raise ValidationError("Invalid quantity: " + qty_str)

def validate_price(price_str):
    try:
        price = float(price_str)
        if price < 0:
            raise ValidationError("Price cannot be negative: " + str(price))
        return price
    except ValueError:
        raise ValidationError("Invalid price: " + price_str)

Step 3 — Process CSV with error tracking

Track valid rows, errors, and line numbers for reporting.

python
def process_orders(filename):
    valid_orders = []
    errors = []
    
    try:
        with open(filename, "r") as f:
            reader = csv.DictReader(f)
            
            for line_num, row in enumerate(reader, start=2):  # start=2 (header is line 1)
                try:
                    # Validate each field
                    sku = validate_sku(row["sku"])
                    qty = validate_qty(row["qty"])
                    price = validate_price(row["price"])
                    
                    # If all valid, add to results
                    valid_orders.append({
                        "sku": sku,
                        "qty": qty,
                        "price": price,
                        "total": qty * price
                    })
                    
                except ValidationError as e:
                    errors.append({
                        "line": line_num,
                        "row": row,
                        "error": str(e)
                    })
                except KeyError as e:
                    errors.append({
                        "line": line_num,
                        "row": row,
                        "error": "Missing column: " + str(e)
                    })
    
    except FileNotFoundError:
        print("Error: File", filename, "not found")
        return None, None
    except PermissionError:
        print("Error: No permission to read", filename)
        return None, None
    
    return valid_orders, errors

Step 4 — Generate error report

Show what succeeded and what failed with details.

python
def print_report(valid, errors):
    print("Processing Report")
    print("=" * 50)
    print("Valid orders:", len(valid))
    print("Errors:", len(errors))
    
    if valid:
        total_value = sum(o["total"] for o in valid)
        print("Total value: $", round(total_value, 2))
    
    if errors:
        print("\nError Details:")
        for err in errors:
            print("  Line", err['line'], ":", err['error'])
            print("    Data:", err['row'])

# Run it
valid, errors = process_orders("orders.csv")
if valid is not None:
    print_report(valid, errors)

Step 5 — Export valid orders and error log

Save clean data for processing, errors for review.

python
def export_results(valid, errors):
    # Export valid orders
    if valid:
        with open("valid_orders.csv", "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=["sku", "qty", "price", "total"])
            writer.writeheader()
            writer.writerows(valid)
        print("Valid orders saved to valid_orders.csv")
    
    # Export error log
    if errors:
        with open("error_log.txt", "w") as f:
            f.write("Order Processing Error Log\n")
            f.write("=" * 50 + "\n\n")
            for err in errors:
                f.write("Line " + str(err['line']) + ": " + err['error'] + "\n")
                f.write("  Data: " + str(err['row']) + "\n\n")
        print("Errors saved to error_log.txt")

export_results(valid, errors)

How this helps at work

  • Production-ready → handles bad data without crashing
  • Audit trail → error log shows exactly what failed and why
  • Partial success → processes valid rows even if some fail
  • Reusable pattern → adapt for any CSV import workflow

Key Takeaways

  • try / except → catch errors, prevent crashes
  • Multiple except blocks → handle different errors differently
  • finally → cleanup code that always runs (close files, connections)
  • raise → trigger errors when business rules are violated
  • Custom exceptions → create domain-specific error types
  • Cornerstone → robust CSV processor handles real-world messiness

Next Steps

You have mastered error handling. Next, explore testing and debugging to catch errors before they happen, or dive into logging to track errors in production systems.