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.
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:
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
withstatement 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:
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:
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:
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:
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
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:
- Validate first: Check file existence, permissions, and format before operations
- Catch specific errors: Handle FileNotFoundError, PermissionError, etc. separately
- Provide context: Include filename and operation in error messages
- Graceful degradation: Return None or default values when appropriate
- 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:
Always Use with
Use the with statement for all file operations to ensure files are closed.
Handle Specific Exceptions
Catch specific exception types (FileNotFoundError, PermissionError) rather than catching all exceptions.
Provide Clear Messages
Give users helpful error messages that explain what went wrong.
Validate Input
Check input before processing and raise exceptions for invalid data.
Use pathlib
Use pathlib.Path for cross-platform path handling.
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
loggingmodule) - 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
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 YourselfTry building a complete program that combines file handling and error handling:
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?