You are viewing a preview of this lesson. Sign in to start learning
Back to Python Programming

Error Handling & Files

Managing errors and working with file systems

Error Handling & Files in Python

Master Python error handling and file operations with free flashcards and spaced repetition practice. This lesson covers exception handling with try-except blocks, working with different file modes, and best practices for reading and writing dataβ€”essential concepts for building robust Python applications.

πŸ’» Welcome to one of Python's most practical topics! Every program encounters errors, and every useful application needs to save or load data. By mastering error handling and file operations, you'll write code that doesn't crash when things go wrong and can persist data beyond a single program run.


Core Concepts

πŸ›‘οΈ Exception Handling: The Basics

In Python, errors that occur during execution are called exceptions. Without proper handling, exceptions cause your program to crash. The try-except block lets you catch and handle these errors gracefully.

Basic Structure:

try:
    # Code that might raise an exception
    risky_operation()
except ExceptionType:
    # Code that runs if exception occurs
    handle_error()

πŸ’‘ Think of try-except like a safety net: You attempt something risky in the try block, and if it fails, the except block catches you before you hit the ground.

πŸ“‹ Common Python Exceptions

Exception TypeWhen It OccursExample
ValueErrorInvalid value for operationint("hello")
TypeErrorOperation on wrong type"5" + 5
FileNotFoundErrorFile doesn't existopen("missing.txt")
ZeroDivisionErrorDivision by zero10 / 0
KeyErrorDictionary key not founddict["nonexistent"]
IndexErrorList index out of rangelist[99]

🎯 Multiple Exception Handling

You can catch different exceptions and handle them differently:

try:
    value = int(input("Enter a number: "))
    result = 100 / value
except ValueError:
    print("That's not a valid number!")
except ZeroDivisionError:
    print("Cannot divide by zero!")
except Exception as e:
    print(f"Unexpected error: {e}")

The order matters! Python checks exceptions from top to bottom, so put specific exceptions before general ones.

🧠 Memory device - VEST: ValueError, Exception (general), Specific first, Then general

πŸ”„ The Complete Try-Except Structure

Python offers four clauses for comprehensive error handling:

try:
    ↓ Attempt risky code
    risk_operation()
except ExceptionType:
    ↓ Handle specific error
    handle_error()
else:
    ↓ Runs if NO exception occurred
    success_actions()
finally:
    ↓ ALWAYS runs (cleanup)
    cleanup_resources()

Key points:

  • try: Required - contains code that might fail
  • except: Required - handles the exception
  • else: Optional - runs only if try succeeds
  • finally: Optional - runs no matter what (perfect for cleanup)
try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("File not found!")
else:
    print("File read successfully!")
finally:
    file.close()  # Always close the file

πŸš€ Raising Exceptions

You can raise exceptions intentionally to signal errors in your own code:

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    if age > 150:
        raise ValueError("Age seems unrealistic!")
    return age

πŸ’‘ Best practice: Raise exceptions early to fail fast and make debugging easier.

πŸ“ File Operations: Opening and Closing Files

Python's open() function creates a file object for reading or writing. The most important parameter is the mode:

ModeDescriptionCreates New File?Overwrites?
"r"Read (default)No - error if missingN/A
"w"WriteYesYES - deletes content!
"a"AppendYesNo - adds to end
"r+"Read and WriteNo - error if missingNo
"x"Exclusive createYes - error if existsN/A

⚠️ WARNING: Mode "w" will erase all existing content immediately upon opening! Use "a" to preserve existing data.

Add "b" for binary mode (e.g., "rb", "wb") when working with images, videos, or other non-text files.

πŸ” The "with" Statement (Context Manager)

The with statement automatically handles file closing, even if errors occur:

with open("data.txt", "r") as file:
    content = file.read()
    # File automatically closes after this block

Why use "with"?

  • βœ… Automatically closes files (no memory leaks)
  • βœ… Closes even if exception occurs
  • βœ… Cleaner, more readable code
  • βœ… No need to remember file.close()

🧠 Memory device - WAS: With Auto Shuts (files)

πŸ“– Reading Files: Three Methods

MethodReturnsBest For
read()Entire file as one stringSmall files
readline()Single line (with \n)Processing line by line
readlines()List of all linesWhen you need all lines as list
# Method 1: Read entire file
with open("story.txt", "r") as file:
    content = file.read()
    print(content)

# Method 2: Read line by line (memory efficient)
with open("story.txt", "r") as file:
    for line in file:  # Iterates naturally
        print(line.strip())  # strip() removes \n

# Method 3: Read all lines into list
with open("story.txt", "r") as file:
    lines = file.readlines()
    print(lines[0])  # Access first line

πŸ’‘ Pro tip: For large files, iterate directly over the file object (for line in file) instead of using readlines() - it's more memory efficient!

✍️ Writing Files: Two Methods

# write() - writes a string
with open("output.txt", "w") as file:
    file.write("Hello, World!\n")
    file.write("Second line\n")

# writelines() - writes list of strings
lines = ["First line\n", "Second line\n", "Third line\n"]
with open("output.txt", "w") as file:
    file.writelines(lines)

⚠️ Important: write() and writelines() don't add newlines automatically - you must include \n yourself!

πŸ” File Existence Checking

Before opening a file, you might want to check if it exists:

import os

if os.path.exists("data.txt"):
    with open("data.txt", "r") as file:
        content = file.read()
else:
    print("File not found!")

Or use exception handling:

try:
    with open("data.txt", "r") as file:
        content = file.read()
except FileNotFoundError:
    print("File not found!")

πŸ’‘ Python philosophy: "It's Easier to Ask Forgiveness than Permission" (EAFP) - use try-except rather than checking first.


πŸ’‘ Practical Examples

Example 1: Safe Number Input with Error Handling

def get_number():
    """Get a valid number from user with error handling"""
    while True:
        try:
            value = float(input("Enter a number: "))
            return value
        except ValueError:
            print("Invalid input! Please enter a number.")
        except KeyboardInterrupt:
            print("\nOperation cancelled by user.")
            return None

# Usage
num = get_number()
if num is not None:
    print(f"You entered: {num}")

Why this works:

  • The while True loop continues until valid input is received
  • ValueError catches non-numeric input
  • KeyboardInterrupt catches Ctrl+C gracefully
  • Returns None on cancel, allowing calling code to handle it

Example 2: Reading and Processing a CSV-like File

def read_student_scores(filename):
    """Read student scores and calculate average"""
    try:
        with open(filename, "r") as file:
            students = {}
            for line in file:
                name, score = line.strip().split(",")
                students[name] = float(score)
            return students
    except FileNotFoundError:
        print(f"Error: {filename} not found!")
        return {}
    except ValueError:
        print("Error: Invalid data format in file!")
        return {}

# Usage
scores = read_student_scores("scores.txt")
if scores:
    average = sum(scores.values()) / len(scores)
    print(f"Class average: {average:.2f}")

Key techniques:

  • Uses with for automatic file closing
  • strip() removes whitespace and newlines
  • split(",") separates name and score
  • Multiple except blocks handle different errors
  • Returns empty dict on error (safe default)

Example 3: Logging Program Events to File

import datetime

def log_event(message, log_file="program.log"):
    """Append timestamped message to log file"""
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] {message}\n"
    
    try:
        with open(log_file, "a") as file:  # "a" = append mode
            file.write(log_entry)
    except PermissionError:
        print(f"Error: No permission to write to {log_file}")
    except Exception as e:
        print(f"Unexpected error: {e}")

# Usage
log_event("Program started")
log_event("User logged in")
log_event("Error occurred in module X")
log_event("Program ended")

Important features:

  • Append mode ("a") preserves existing logs
  • Timestamps make logs useful for debugging
  • Catches PermissionError (common in protected directories)
  • Generic Exception catches unexpected errors

Example 4: Configuration File Reader with Defaults

def load_config(filename="config.txt"):
    """Load configuration with fallback defaults"""
    # Default configuration
    config = {
        "username": "guest",
        "theme": "light",
        "max_connections": 10
    }
    
    try:
        with open(filename, "r") as file:
            for line in file:
                if "=" in line and not line.startswith("#"):
                    key, value = line.strip().split("=", 1)
                    config[key.strip()] = value.strip()
        print(f"Configuration loaded from {filename}")
    except FileNotFoundError:
        print(f"Config file not found, using defaults")
    except Exception as e:
        print(f"Error reading config: {e}, using defaults")
    finally:
        return config

# Usage
settings = load_config()
print(f"Theme: {settings['theme']}")
print(f"Max connections: {settings['max_connections']}")

Design patterns:

  • Provides sensible defaults (graceful degradation)
  • Skips comment lines (starting with #)
  • split("=", 1) splits only on first = (handles values with =)
  • finally ensures config is always returned
  • Robust against missing or malformed files

πŸ”§ Try this: Create a config.txt file with contents:

username=john
theme=dark
# This is a comment
max_connections=25

Run the code and observe how it overrides defaults!


⚠️ Common Mistakes

Mistake 1: Forgetting to Close Files

❌ Wrong:

file = open("data.txt", "r")
content = file.read()
# Forgot file.close() - resource leak!

βœ… Right:

with open("data.txt", "r") as file:
    content = file.read()
# Automatically closed

Mistake 2: Catching Exception Too Broadly Too Early

❌ Wrong:

try:
    value = int(input())
    result = 100 / value
except Exception:  # Too broad!
    print("Something went wrong")
    # Can't tell if ValueError or ZeroDivisionError

βœ… Right:

try:
    value = int(input())
    result = 100 / value
except ValueError:
    print("Invalid number format")
except ZeroDivisionError:
    print("Cannot divide by zero")

Mistake 3: Using "w" Mode When You Mean "a"

❌ Wrong:

with open("log.txt", "w") as file:  # ERASES entire file!
    file.write("New log entry\n")

βœ… Right:

with open("log.txt", "a") as file:  # Appends to end
    file.write("New log entry\n")

Mistake 4: Not Handling File Encoding

❌ Wrong:

with open("unicode_file.txt", "r") as file:
    content = file.read()  # May fail on special characters

βœ… Right:

with open("unicode_file.txt", "r", encoding="utf-8") as file:
    content = file.read()  # Handles international characters

Mistake 5: Catching but Not Handling

❌ Wrong:

try:
    risky_operation()
except Exception:
    pass  # Silent failure - very bad!

βœ… Right:

try:
    risky_operation()
except Exception as e:
    print(f"Error occurred: {e}")
    # Or log it, or handle appropriately

πŸ€” Did you know? The pass statement in an except block is called "swallowing exceptions" and is considered a code smell. It hides bugs and makes debugging nearly impossible!


🎯 Key Takeaways

βœ… Use try-except blocks to handle errors gracefully instead of crashing

βœ… Always use "with" statement for file operations - it handles closing automatically

βœ… Catch specific exceptions first, then more general ones

βœ… "w" mode erases files, use "a" to append, "r+" to read and write

βœ… The finally block always executes - perfect for cleanup operations

βœ… Raise exceptions in your own code when something goes wrong

βœ… Iterate over file objects directly for memory-efficient line-by-line reading

βœ… Specify encoding="utf-8" when working with international text


πŸ“š Further Study

  1. Python Official Documentation - Errors and Exceptions: https://docs.python.org/3/tutorial/errors.html - Comprehensive guide to Python's exception handling

  2. Real Python - Reading and Writing Files: https://realpython.com/read-write-files-python/ - In-depth tutorial with advanced techniques

  3. Python Official Documentation - File Objects: https://docs.python.org/3/glossary.html#term-file-object - Technical reference for file operations and modes


πŸ“‹ Quick Reference Card

Try-Except Structuretry β†’ except β†’ else β†’ finally
File Mode "r"Read only (default)
File Mode "w"Write (erases existing!)
File Mode "a"Append to end
File Mode "r+"Read and write
with statementAuto-closes files
file.read()Read entire file
file.readline()Read single line
file.readlines()Read all lines as list
file.write(s)Write string (no auto \n)
raise ExceptionManually trigger error
Common ExceptionsValueError, TypeError, FileNotFoundError, ZeroDivisionError

Code Template:

try:
    with open("file.txt", "r", encoding="utf-8") as f:
        data = f.read()
except FileNotFoundError:
    print("File not found")
except Exception as e:
    print(f"Error: {e}")
finally:
    print("Cleanup complete")

Practice Questions

Test your understanding with these questions:

Q1: Complete the exception raising code: ```python def set_age(age): if age < 0: {{1}} ValueError("{{2}}") return age ```
A: ["raise","Age cannot be negative"]
Q2: Write a Python function that reads a file and returns its line count. The function should handle FileNotFoundError by returning 0.
A: !AI