Python File and Error Handling

Working with files and handling errors gracefully are essential skills for any Python developer.

File Operations

Python
# Reading files
with open("data.txt", "r") as file:
    content = file.read()        # Read entire file as string
    print(content)

# Reading line by line (memory efficient for large files)
with open("large_file.txt", "r") as file:
    for line in file:
        print(line.strip())     # strip() removes trailing newline

# Reading all lines into a list
with open("data.txt", "r") as file:
    lines = file.readlines()    # List of strings
    # or
    lines = [line.strip() for line in file]

# Writing files
with open("output.txt", "w") as file:
    file.write("Hello, World!\n")
    file.write("Second line\n")

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

# Writing multiple lines
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
with open("output.txt", "w") as file:
    file.writelines(lines)

Working with Paths

Python
from pathlib import Path

# Create Path objects
p = Path("documents/notes.txt")
home = Path.home()          # User's home directory
cwd = Path.cwd()            # Current working directory

# Path operations
print(p.name)               # "notes.txt"
print(p.stem)               # "notes"
print(p.suffix)             # ".txt"
print(p.parent)             # Path("documents")

# Join paths (cross-platform!)
config_path = home / ".config" / "app" / "settings.json"

# Check existence
if config_path.exists():
    print(f"Found: {config_path}")
    print(f"Size: {config_path.stat().st_size} bytes")

# Create directories
Path("output/reports").mkdir(parents=True, exist_ok=True)

# List files
for p in Path(".").glob("*.py"):       # Current directory only
    print(p)

for p in Path(".").rglob("*.py"):      # Recursive (all subdirs)
    print(p)

# Read/write with pathlib
content = Path("data.txt").read_text()
Path("output.txt").write_text("Hello!")

Exception Handling

Python
# Basic try/except
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero!")

# Multiple exception types
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!")

# Catching and using the exception
try:
    file = open("missing.txt")
except FileNotFoundError as e:
    print(f"File not found: {e}")

# try/except/else/finally
try:
    data = open("config.json").read()
except FileNotFoundError:
    data = "{}"  # Default
else:
    print("File loaded successfully!")  # Runs if NO exception
finally:
    print("This always runs!")  # Cleanup code

Custom Exceptions

Python
# Custom exception classes
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"Cannot withdraw ${amount:.2f}. "
            f"Current balance: ${balance:.2f}"
        )

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance

# Using custom exceptions
account = BankAccount(100)
try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)  # "Cannot withdraw $150.00. Current balance: $100.00"
    print(f"Short by: ${abs(e.balance - e.amount):.2f}")

Context Managers

Python
# The 'with' statement is a context manager
# It automatically handles setup and cleanup

class DatabaseConnection:
    def __init__(self, url):
        self.url = url
        self.connection = None
    
    def __enter__(self):
        print(f"Connecting to {self.url}...")
        self.connection = True  # Simulate connection
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing connection...")
        self.connection = False
        return False  # Don't suppress exceptions
    
    def query(self, sql):
        return f"Results for: {sql}"

# Usage
with DatabaseConnection("postgres://localhost/mydb") as db:
    results = db.query("SELECT * FROM users")
    print(results)
# Automatically closes when exiting the block

Knowledge Check

4 questions — test your understanding

1

What is the purpose of the `with` statement when working with files?

2

Which exception is raised when dividing by zero?

3

What does pathlib.Path('a/b/c').stem return?

4

When does the `else` block in try/except/else/finally run?