Working with Files

Reading from and writing to files in Python

Introduction

Working with files is essential for many programming tasks. Whether you're reading configuration files, processing data, saving user input, or logging information, Python provides simple and powerful tools for file operations. This lesson covers reading from files, writing to files, and best practices for safe file handling.

Opening Files

Before you can read or write to a file, you need to open it. Python's open() function is used for this purpose.

Basic File Opening

# Open a file for reading
file = open("data.txt", "r")
content = file.read()
file.close()  # Always close files!

# Open a file for writing
file = open("output.txt", "w")
file.write("Hello, World!")
file.close()

# File modes:
# "r" - Read (default)
# "w" - Write (overwrites existing file)
# "a" - Append (adds to end of file)
# "x" - Exclusive creation (fails if file exists)
# "b" - Binary mode (e.g., "rb", "wb")
# "t" - Text mode (default, e.g., "rt", "wt")
# "+" - Read and write (e.g., "r+", "w+")

The with Statement (Recommended)

The with statement automatically closes files, even if an error occurs. This is the recommended way to work with files:

# Using 'with' statement (recommended)
with open("data.txt", "r") as file:
    content = file.read()
    # File is automatically closed when block exits
    # Even if an error occurs!

# Equivalent to:
file = open("data.txt", "r")
try:
    content = file.read()
finally:
    file.close()  # Always executed

Best Practice: Always use the with statement when working with files. It ensures files are properly closed and prevents resource leaks.

Reading Files

Python provides several methods for reading file content, each useful for different scenarios.

Reading Entire File

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

# Read entire file as list of lines
with open("data.txt", "r") as file:
    lines = file.readlines()
    for line in lines:
        print(line.strip())  # strip() removes newline characters

Reading Line by Line

For large files, reading line by line is memory-efficient:

# Method 1: Iterate directly over file object
with open("data.txt", "r") as file:
    for line in file:
        print(line.strip())  # Process each line

# Method 2: Using readline() in a loop
with open("data.txt", "r") as file:
    line = file.readline()
    while line:
        print(line.strip())
        line = file.readline()

# Method 3: Read specific number of characters
with open("data.txt", "r") as file:
    chunk = file.read(100)  # Read first 100 characters
    print(chunk)

Reading with Error Handling

def read_file_safely(filename):
    """Read file content with proper error handling."""
    try:
        with open(filename, "r") as file:
            content = file.read()
            return content
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found.")
        return None
    except PermissionError:
        print(f"Error: Permission denied to read '{filename}'.")
        return None
    except Exception as e:
        print(f"Unexpected error: {e}")
        return None

# Usage
content = read_file_safely("data.txt")
if content:
    print("File read successfully!")
    print(content)

Writing Files

Writing to files allows you to save data, create logs, and generate output files.

Writing Text

# Write mode ("w") - overwrites existing file
with open("output.txt", "w") as file:
    file.write("Hello, World!\n")
    file.write("This is line 2\n")
    file.write("This is line 3")

# Append mode ("a") - adds to end of file
with open("output.txt", "a") as file:
    file.write("\nThis line is appended")

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

# Or use write() with join
with open("output.txt", "w") as file:
    file.write("\n".join(["Line 1", "Line 2", "Line 3"]))

Write vs Append

Write Mode ("w")
  • Overwrites existing file
  • Creates new file if it doesn't exist
  • Use for creating new files
  • Deletes existing content
Append Mode ("a")
  • Adds to end of existing file
  • Creates new file if it doesn't exist
  • Use for logs or accumulating data
  • Preserves existing content

Writing with Error Handling

def write_file_safely(filename, content):
    """Write content to file with error handling."""
    try:
        with open(filename, "w") as file:
            file.write(content)
        print(f"Successfully wrote to {filename}")
        return True
    except PermissionError:
        print(f"Error: Permission denied to write to '{filename}'.")
        return False
    except Exception as e:
        print(f"Unexpected error: {e}")
        return False

# Usage
success = write_file_safely("output.txt", "Hello, World!")
if success:
    print("File written successfully!")

Working with File Paths

Understanding file paths is important for working with files in different locations.

Relative vs Absolute Paths

# Relative path (relative to current working directory)
with open("data.txt", "r") as file:
    content = file.read()

# Relative path with subdirectory
with open("data/input.txt", "r") as file:
    content = file.read()

# Absolute path (full path from root)
with open("/home/user/documents/data.txt", "r") as file:
    content = file.read()

# On Windows
with open("C:\\Users\\User\\Documents\\data.txt", "r") as file:
    content = file.read()

Using os.path (Cross-Platform)

The os.path module helps create cross-platform paths:

import os

# Join path components (handles / or \ automatically)
file_path = os.path.join("data", "input", "file.txt")
# On Linux/Mac: "data/input/file.txt"
# On Windows: "data\input\file.txt"

with open(file_path, "r") as file:
    content = file.read()

# Get current working directory
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")

# Check if file exists
if os.path.exists("data.txt"):
    print("File exists!")
else:
    print("File does not exist!")

# Get absolute path
abs_path = os.path.abspath("data.txt")
print(f"Absolute path: {abs_path}")

Using pathlib (Modern Approach)

The pathlib module (Python 3.4+) provides an object-oriented approach:

from pathlib import Path

# Create path object
file_path = Path("data") / "input" / "file.txt"
# Works on all platforms!

# Read file
with open(file_path, "r") as file:
    content = file.read()

# Or use pathlib's read_text method
content = file_path.read_text()

# Write file
file_path.write_text("Hello, World!")

# Check if exists
if file_path.exists():
    print("File exists!")

# Get parent directory
parent = file_path.parent
print(f"Parent: {parent}")

# Get filename
filename = file_path.name
print(f"Filename: {filename}")

Common File Operations

Processing CSV-like Data

# Reading and processing a simple CSV file
with open("data.csv", "r") as file:
    for line in file:
        # Split by comma (simple CSV parsing)
        fields = line.strip().split(",")
        if len(fields) >= 2:
            name = fields[0]
            age = fields[1]
            print(f"Name: {name}, Age: {age}")

# Writing CSV-like data
data = [
    ["Alice", "30", "Engineer"],
    ["Bob", "25", "Designer"],
    ["Charlie", "35", "Manager"]
]

with open("output.csv", "w") as file:
    for row in data:
        file.write(",".join(row) + "\n")

Reading Configuration Files

# Simple key=value configuration file
config = {}
with open("config.txt", "r") as file:
    for line in file:
        line = line.strip()
        if line and not line.startswith("#"):  # Skip empty lines and comments
            key, value = line.split("=", 1)
            config[key.strip()] = value.strip()

print(config)
# Example config.txt:
# host=localhost
# port=8080
# debug=True

Logging to Files

import datetime

def log_message(message, log_file="app.log"):
    """Append timestamped message to log file."""
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] {message}\n"

    with open(log_file, "a") as file:
        file.write(log_entry)

# Usage
log_message("Application started")
log_message("Processing data...")
log_message("Application finished")

# Log file contains:
# [2024-01-15 10:30:00] Application started
# [2024-01-15 10:30:05] Processing data...
# [2024-01-15 10:30:10] Application finished

Binary Files

For binary files (images, executables, etc.), use binary mode:

# Reading binary file
with open("image.jpg", "rb") as file:
    binary_data = file.read()
    # Process binary data...

# Writing binary file
with open("copy.jpg", "wb") as file:
    file.write(binary_data)

# Copying a file (binary mode)
def copy_file(source, destination):
    with open(source, "rb") as src:
        with open(destination, "wb") as dst:
            dst.write(src.read())

copy_file("original.jpg", "copy.jpg")

Best Practices

✓ Do
  • Always use with statement
  • Handle file errors with try/except
  • Use appropriate file modes
  • Check if file exists before reading
  • Use pathlib for modern code
  • Close files explicitly if not using with
✗ Don't
  • Forget to close files
  • Ignore file errors
  • Use hardcoded paths
  • Read entire large files into memory
  • Write without error handling
  • Assume file exists

Key Takeaways

Opening Files

  • Use open() function
  • Always use with statement
  • Choose appropriate mode (r, w, a)
  • Files auto-close with with

Reading Files

  • read() - entire file
  • readline() - one line
  • readlines() - all lines
  • Iterate for large files

Writing Files

  • write() - write string
  • writelines() - write list
  • "w" mode overwrites
  • "a" mode appends

File Paths

  • Use os.path for compatibility
  • Use pathlib for modern code
  • Handle relative/absolute paths
  • Check file existence
What's Next?

Great work! You now know how to read from and write to files. This is a fundamental skill for many real-world applications. In future lessons, we'll explore:

  • Modules and packages - Organize and reuse your Python code effectively
  • String manipulation - Master string operations, slicing, and modern formatting
  • List comprehensions - Write cleaner, more Pythonic code with elegant one-liners
  • Standard library essentials - Leverage powerful built-in tools like datetime, json, and collections