Unit 7 • Lesson 10

File and Error Handling in Real Projects

Overview

You'll combine file I/O and exception handling to build practical programs, such as data loggers or user input recorders. These examples reinforce how to manage files and handle errors in real-world applications, demonstrating professional coding practices.

Intermediate 30–40 min

What You Will Learn in This Lesson

By the end of this lesson, you will know:

  • Real-world patterns: See how file and error handling work together in practical programs.
  • Data logging: Create programs that safely log data to files.
  • User input handling: Safely process and save user input with error handling.
  • Best practices: Combine all the concepts you've learned into robust programs.
  • Complete examples: Build full programs that handle files and errors properly.

Project 1: Data Logger

Let's build a program that logs data to a file, handling errors gracefully:

Complete Data Logger
from datetime import datetime
from pathlib import Path

def log_data(message, log_file="app.log"):
    """Safely log a message to a file"""
    try:
        log_path = Path(log_file)
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        with open(log_path, "a") as file:
            file.write(f"[{timestamp}] {message}\n")
        print(f"Logged: {message}")
        
    except PermissionError:
        print(f"Error: Cannot write to {log_file}. Permission denied.")
    except Exception as e:
        print(f"Unexpected error while logging: {e}")

# Usage
log_data("Application started")
log_data("User logged in")
log_data("Processing data...")

Key Features

  • Uses with statement for automatic file closing
  • Handles PermissionError specifically
  • Uses append mode to preserve existing logs
  • Includes timestamps for each log entry

Project 2: User Input Recorder

A program that safely saves user input to a file:

User Input Recorder
def save_user_input():
    """Safely save user input to a file"""
    filename = "user_data.txt"
    
    try:
        name = input("Enter your name: ")
        if not name:
            raise ValueError("Name cannot be empty!")
        
        age = input("Enter your age: ")
        try:
            age = int(age)
            if age < 0 or age > 150:
                raise ValueError("Age must be between 0 and 150!")
        except ValueError:
            raise ValueError("Age must be a valid number!")
        
        # Save to file
        with open(filename, "a") as file:
            file.write(f"Name: {name}, Age: {age}\n")
        
        print(f"Data saved successfully to {filename}")
        
    except ValueError as e:
        print(f"Invalid input: {e}")
    except PermissionError:
        print(f"Error: Cannot write to {filename}. Permission denied.")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")

# Run the program
save_user_input()

Project 3: File Reader with Error Handling

A robust file reader that handles various error cases:

Robust File Reader
from pathlib import Path

def read_file_safely(filename):
    """Read a file with comprehensive error handling"""
    file_path = Path(filename)
    
    # Check if file exists
    if not file_path.exists():
        raise FileNotFoundError(f"File '{filename}' does not exist!")
    
    # Check if it's actually a file
    if not file_path.is_file():
        raise ValueError(f"'{filename}' is not a file!")
    
    try:
        with open(file_path, "r") as file:
            content = file.read()
            return content
            
    except PermissionError:
        raise PermissionError(f"Cannot read '{filename}'. Permission denied.")
    except UnicodeDecodeError:
        raise ValueError(f"Cannot read '{filename}'. File encoding issue.")
    except Exception as e:
        raise Exception(f"Error reading '{filename}': {e}")

# Usage with error handling
try:
    content = read_file_safely("data.txt")
    print(content)
except FileNotFoundError as e:
    print(f"File error: {e}")
except PermissionError as e:
    print(f"Permission error: {e}")
except Exception as e:
    print(f"Error: {e}")

Project 4: Configuration File Handler

A program that reads and validates configuration files:

Configuration Handler
import json
from pathlib import Path

class ConfigError(Exception):
    """Custom exception for configuration errors"""
    pass

def load_config(config_file="config.json"):
    """Load and validate configuration file"""
    config_path = Path(config_file)
    
    # Check if file exists
    if not config_path.exists():
        raise FileNotFoundError(f"Config file '{config_file}' not found!")
    
    try:
        with open(config_path, "r") as file:
            config = json.load(file)
            
        # Validate required fields
        required_fields = ["host", "port", "database"]
        for field in required_fields:
            if field not in config:
                raise ConfigError(f"Missing required field: {field}")
        
        return config
        
    except json.JSONDecodeError:
        raise ConfigError(f"Invalid JSON in '{config_file}'")
    except Exception as e:
        raise ConfigError(f"Error loading config: {e}")

# Usage
try:
    config = load_config("settings.json")
    print(f"Host: {config['host']}")
    print(f"Port: {config['port']}")
except FileNotFoundError as e:
    print(f"Config file error: {e}")
except ConfigError as e:
    print(f"Configuration error: {e}")

Error Handling Patterns

Common patterns for handling errors in file operations:

Pattern 1: Try-Except with Multiple Handlers
def read_file_with_handlers(filename):
    try:
        with open(filename, 'r') as file:
            return file.read()
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found.")
        return None
    except PermissionError:
        print(f"Error: Permission denied for '{filename}'.")
        return None
    except IOError as e:
        print(f"Error reading file: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error: {e}")
        return None
Pattern 2: Validation Before Operations
from pathlib import Path

def safe_file_operation(filename, operation='read'):
    file_path = Path(filename)
    
    # Validate before attempting operation
    if not file_path.exists():
        raise FileNotFoundError(f"File '{filename}' does not exist")
    
    if not file_path.is_file():
        raise ValueError(f"'{filename}' is not a file")
    
    # Perform operation
    try:
        if operation == 'read':
            with open(file_path, 'r') as f:
                return f.read()
        elif operation == 'write':
            # Write operation here
            pass
    except PermissionError:
        raise PermissionError(f"Cannot {operation} '{filename}'. Permission denied.")

Error Handling Strategy

Follow this strategy for robust error handling:

  1. Validate first: Check file existence, permissions, and format before operations
  2. Catch specific errors: Handle FileNotFoundError, PermissionError, etc. separately
  3. Provide context: Include filename and operation in error messages
  4. Graceful degradation: Return None or default values when appropriate
  5. Log errors: Consider logging errors for debugging (we'll cover this in advanced topics)

Best Practices Summary

Here are the key practices we've used in these projects:

1

Always Use with

Use the with statement for all file operations to ensure files are closed.

2

Handle Specific Exceptions

Catch specific exception types (FileNotFoundError, PermissionError) rather than catching all exceptions.

3

Provide Clear Messages

Give users helpful error messages that explain what went wrong.

4

Validate Input

Check input before processing and raise exceptions for invalid data.

5

Use pathlib

Use pathlib.Path for cross-platform path handling.

6

Test Error Cases

Test your code with missing files, invalid permissions, and corrupted data.

Real-World Considerations

When building production applications, consider these additional factors:

Production-Ready Error Handling

  • Logging: Log errors for debugging and monitoring (use Python's logging module)
  • User-friendly messages: Don't expose technical details to end users
  • Retry logic: For network operations, implement retry mechanisms
  • Resource cleanup: Always ensure resources are freed, even on errors
  • Error recovery: Provide fallback options when possible
  • Testing: Write tests for error conditions, not just success cases
Example: Robust File Processor
from pathlib import Path

def process_file(filename, output_file):
    """Process a file with comprehensive error handling"""
    input_path = Path(filename)
    output_path = Path(output_file)
    
    # Validate input file
    if not input_path.exists():
        raise FileNotFoundError(f"Input file '{filename}' not found")
    
    if not input_path.is_file():
        raise ValueError(f"'{filename}' is not a file")
    
    # Process file
    try:
        with open(input_path, 'r') as infile:
            data = infile.read()
            # Process data here
            processed_data = data.upper()  # Example processing
        
        # Write output
        try:
            with open(output_path, 'w') as outfile:
                outfile.write(processed_data)
            print(f"Successfully processed '{filename}' -> '{output_file}'")
        except PermissionError:
            raise PermissionError(f"Cannot write to '{output_file}'. Permission denied.")
            
    except PermissionError:
        raise PermissionError(f"Cannot read '{filename}'. Permission denied.")
    except UnicodeDecodeError:
        raise ValueError(f"Cannot read '{filename}'. File encoding issue.")
    except Exception as e:
        raise Exception(f"Error processing file: {e}")

# Usage
try:
    process_file("input.txt", "output.txt")
except (FileNotFoundError, PermissionError, ValueError) as e:
    print(f"Error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Practice: Complete Program

Try It Yourself

Try building a complete program that combines file handling and error handling:

Click "Run Code" to see your output here

What happened? This program safely saves notes to a file, validating input and handling errors gracefully.

Summary

In this lesson, you've seen how to combine all the concepts you've learned:

  • File operations: Reading and writing files safely
  • Error handling: Catching and handling exceptions gracefully
  • The with statement: Ensuring files are always closed
  • Input validation: Checking data before processing
  • Custom exceptions: Creating clear error types
  • Real-world patterns: Building complete, robust programs

Congratulations!

You've completed Unit 7! You now know how to work with files and handle errors professionally. These skills are essential for building real-world Python applications.

End-of-Lesson Exercises

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

Exercise 1: Design a Program

Design a program that reads student names and grades from a file, calculates averages, and writes results to a new file. What error handling would you include?

Exercise 2: Best Practices

What are the three most important practices you learned for handling files and errors in Python?