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
# First create a file to read, so the examples below have something to open
# (writing is covered in detail further down)
with open("data.txt", "w") as file:
file.write("First line\nSecond line\nThird line\n")
# Open a file for reading
file = open("data.txt", "r")
content = file.read()
file.close() # Always close files!
print(content)
# Open a file for writing
file = open("output.txt", "w")
file.write("Hello, World!")
file.close()Expected Output:
First line Second line Third line
File Modes
The second argument to open() is the mode. It has two parts: an access mode that says what you intend to do with the file, and optional modifiers that change how the data is handled. If you omit the mode entirely, Python uses "r".
| Mode | Meaning | If the file already exists | If it does not exist |
|---|---|---|---|
"r" | Read (default) | Opens it, positioned at the start | FileNotFoundError |
"w" | Write | Erases the contents immediately | Creates it |
"a" | Append | Keeps it; writes go to the end | Creates it |
"x" | Exclusive creation | FileExistsError | Creates it |
The remaining three characters are modifiers, not modes of their own. You add them to one of the four above:
"t"- text mode, the default. Reading gives you astr, and Python translates line endings for you."b"- binary mode. Reading gives youbyteswith no decoding or line-ending translation:"rb","wb"."+"- adds the missing half of read/write to the access mode, so the file is open for both:"r+"reads and writes without erasing, while"w+"still erases the file first.
Careful: "w" truncates the file the moment you open it, before you write anything. If you meant to add to a file rather than replace it, use "a". When a file must not be overwritten by accident,"x" makes that a hard error instead of silent data loss.
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!
print("Closed automatically:", file.closed)
# Equivalent to:
file = open("data.txt", "r")
try:
content = file.read()
finally:
file.close() # Always executed
print("Closed manually:", file.closed)Expected Output:
Closed automatically: True Closed manually: True
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 charactersExpected Output:
First line Second line Third line First line Second line Third line
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)Expected Output:
First line Second line Third line First line Second line Third line First line Second line Third line
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)Expected Output:
File read successfully! First line Second line Third line
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"]))
# Each "w" wiped the file, so only the last version survives
with open("output.txt", "r") as file:
print(file.read())Expected Output:
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!")Expected Output:
Successfully wrote to output.txt File written successfully!
Working with File Paths
Understanding file paths is important for working with files in different locations.
Relative vs Absolute Paths
import os
# Relative path (relative to current working directory)
with open("data.txt", "r") as file:
content = file.read()
print("data.txt:", len(content), "characters")
# Relative path with subdirectory
os.makedirs("data", exist_ok=True)
with open("data/input.txt", "w") as file:
file.write("nested file\n")
with open("data/input.txt", "r") as file:
content = file.read()
print("data/input.txt:", content.strip())
# Absolute paths start from the root of the filesystem. These are shown as
# syntax only, since they will not exist on your machine:
#
# with open("/home/user/documents/data.txt", "r") as file:
# content = file.read()
#
# On Windows a backslash starts an escape, so double them (or use a raw string):
#
# with open("C:\\Users\\User\\Documents\\data.txt", "r") as file:
# content = file.read()Expected Output:
data.txt: 34 characters data/input.txt: nested file
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")
print(f"Joined path: {file_path}")
# On Linux/Mac: "data/input/file.txt"
# On Windows: "data\input\file.txt"
# Create it so there is something to read
os.makedirs(os.path.join("data", "input"), exist_ok=True)
with open(file_path, "w") as file:
file.write("nested file\n")
with open(file_path, "r") as file:
content = file.read()
print(f"Contents: {content.strip()}")
# Check if file exists
if os.path.exists("data.txt"):
print("File exists!")
else:
print("File does not exist!")
# The next two depend on where you run the script, so your paths will differ
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
abs_path = os.path.abspath("data.txt")
print(f"Absolute path: {abs_path}")Expected Output:
Joined path: data/input/file.txt Contents: nested file File exists! Current directory: /tmp/file-demo Absolute path: /tmp/file-demo/data.txt
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"
print(file_path) # Works on all platforms!
# Make sure the folder exists, then write through the Path itself
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text("Hello, World!")
# Read it back with pathlib's read_text method
content = file_path.read_text()
print(f"Contents: {content}")
# 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}")Expected Output:
data/input/file.txt Contents: Hello, World! File exists! Parent: data/input Filename: file.txt
Common File Operations
Processing CSV-like Data
# Create a small CSV to work with
with open("data.csv", "w") as file:
file.write("Alice,30\nBob,25\nCharlie,35\n")
# 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")Expected Output:
Name: Alice, Age: 30 Name: Bob, Age: 25 Name: Charlie, Age: 35
Reading Configuration Files
# Create the configuration file this example reads
with open("config.txt", "w") as file:
file.write("# App settings\nhost=localhost\nport=8080\ndebug=True\n")
# 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)Expected Output:
{'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")
# Read the log back. "a" appends, so running this twice doubles the entries.
with open("app.log", "r") as file:
print(file.read(), end="")Expected Output:
[2026-09-05 17:25:28] Application started [2026-09-05 17:25:28] Processing data... [2026-09-05 17:25:28] Application finished
Binary Files
For binary files (images, executables, etc.), use binary mode:
# Create a small binary file so the examples have something to read.
# These four bytes are the real magic number that starts every JPEG.
with open("image.jpg", "wb") as file:
file.write(bytes([0xFF, 0xD8, 0xFF, 0xE0]) + b"...not a real image...")
# Reading binary file
with open("image.jpg", "rb") as file:
binary_data = file.read()
print(f"Read {len(binary_data)} bytes")
print(f"First 4 bytes: {binary_data[:4]}")
# 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("image.jpg", "copy2.jpg")
with open("copy2.jpg", "rb") as file:
print("Copy is identical:", file.read() == binary_data)Expected Output:
Read 26 bytes First 4 bytes: b'\xff\xd8\xff\xe0' Copy is identical: True
Best Practices
✓ Do
- Always use
withstatement - Handle file errors with try/except
- Use appropriate file modes
- Catch
FileNotFoundErrorrather than checking existence first - Use
pathlibfor 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
withstatement - Choose appropriate mode (r, w, a)
- Files auto-close with
with
Reading Files
read()- entire filereadline()- one linereadlines()- all lines- Iterate for large files
Writing Files
write()- write stringwritelines()- write list- "w" mode overwrites
- "a" mode appends
File Paths
- Use
os.pathfor compatibility - Use
pathlibfor 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