File Handling in Python
Read, write, and manage files safely
Almost every real Python program interacts with files. This module takes you from opening your first file to processing CSV, JSON, and binary data with production‑ready patterns.
📂 Lesson 1 — The open() Function
The gateway to every file operation
🔑 Understanding open()
The open() built‑in function creates a file object that lets you read from or write to a file. It requires at least one argument: the file path. A second optional argument — the mode — tells Python what you intend to do.
The most common modes are:
'r'— Read (default). Opens for reading, raises an error if the file doesn't exist.'w'— Write. Creates a new file or truncates an existing one.'a'— Append. Creates or opens a file for writing at the end.'b'— Binary mode. Used with other modes (e.g.'rb').
Always specify the encoding explicitly when working with text files. encoding="utf-8" is the safest choice.
# Open a file for reading — explicit encoding is best practice file = open("example.txt", "r", encoding="utf-8") content = file.read() print(f"File content: {content}") file.close() # always close when done
.close() can leak file handles and leave data unflushed. Lesson 5 shows the with statement — the idiomatic way to avoid this.📖 Lesson 2 — Reading Files
.read(), .readline(), .readlines(), and iteration
📄 Methods for Reading
Once a file is open in read mode, you have several ways to consume its content:
.read()— returns the entire file as a single string. Can accept an optional size argument..readline()— returns the next line as a string, or an empty string at EOF..readlines()— returns a list where each element is a line from the file.- Iterating directly over the file object — the most memory‑efficient way for large files.
# Demonstrating different reading methods with open("example.txt", "r", encoding="utf-8") as f: full_text = f.read() print("First 30 chars:", full_text[:30]) with open("example.txt", "r", encoding="utf-8") as f: first_line = f.readline() print("First line:", first_line.strip()) with open("example.txt", "r", encoding="utf-8") as f: for line_num, line in enumerate(f, start=1): print(f"Line {line_num}: {line.strip()}")
.readline()) keeps memory usage low. .read() loads the whole file, which can be problematic for files larger than available RAM.✍️ Lesson 3 — Writing Files
Creating new files with 'w' mode
📝 .write() and .writelines()
Opening a file with mode 'w' creates a new file or overwrites an existing one. Data is written from the current position.
.write(string)— writes a single string. You must add newline characters (\n) yourself..writelines(list_of_strings)— writes each string in the list without adding separators.
# Writing a file with open("output.txt", "w", encoding="utf-8") as f: f.write("Hello, file!\n") f.write("This is a second line.\n") lines = ["First line\n", "Second line\n", "Third line\n"] with open("output.txt", "w", encoding="utf-8") as f: f.writelines(lines) print("File written successfully.")
'w' on an existing file erases its previous content. If you want to add to an existing file without erasing it, use append mode ('a') — see Lesson 4.➕ Lesson 4 — Appending to Files
Adding data without destroying what’s already there
📌 Append Mode ('a')
Opening a file with mode 'a' places the write pointer at the end of the file. If the file doesn’t exist, it is created. This is the correct way to add logs, new records, or incremental data.
# Appending to a file with open("log.txt", "a", encoding="utf-8") as f: f.write("New log entry: process completed.\n") # Verify the content with open("log.txt", "r", encoding="utf-8") as f: print(f.read())
'w', it never truncates the file — every write goes to the end.🔒 Lesson 5 — Context Managers (with)
The safest way to work with files
🛡️ Why Use with?
A context manager guarantees that a file is properly closed — even if an exception occurs while processing it. This prevents resource leaks and data corruption. The with statement is the idiomatic, recommended approach in all modern Python code.
# The context manager way — file auto-closes with open("example.txt", "r", encoding="utf-8") as f: content = f.read() print(content) # No need to call f.close() — the with block handles it
with statement: with open(a) as fa, open(b) as fb: — both are closed when the block exits.🗂️ Lesson 6 — pathlib: Modern Path Handling
Object‑oriented paths that work across operating systems
📁 Creating and Manipulating Paths
The pathlib module provides the Path class — a clean, cross‑platform way to work with filesystem paths. It handles slashes, extensions, parent directories, and file existence checks without messy string operations.
from pathlib import Path # Build a path relative to the current directory p = Path("data") / "notes.txt" print(f"Path: {p}") print(f"Name: {p.name}") print(f"Suffix: {p.suffix}") print(f"Parent: {p.parent}") # Check if it exists print(f"Exists: {p.exists()}")
pathlib is the modern way to handle paths. The older os.path module still works, but pathlib is more readable and less error‑prone.📊 Lesson 7 — Working with CSV Files
Structured data in rows and columns
📋 The csv Module
CSV (Comma‑Separated Values) is one of the most common formats for tabular data. Python’s csv module handles quoting, delimiters, and newline issues correctly — things that break naive string splitting.
import csv # Writing CSV rows = [ {"name": "Ana", "score": 95}, {"name": "Bob", "score": 87} ] with open("scores.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["name", "score"]) writer.writeheader() writer.writerows(rows) # Reading CSV with open("scores.csv", "r", newline="", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: print(f"{row['name']}: {row['score']}")
newline="" when opening CSV files on Windows to avoid blank lines. DictReader maps each row to a dictionary using the header row — far safer than positional indexing.📦 Lesson 8 — Working with JSON Files
Structured, nested data for APIs and configuration
📄 The json Module
JSON is the lingua franca of APIs and configuration files. The json module converts Python dictionaries and lists to JSON strings (and back), with pretty‑printing and UTF‑8 support.
import json data = { "user": "Glenn", "level": "advanced", "scores": [95, 88, 92] } # Write JSON with open("user.json", "w", encoding="utf-8") as f: json.dump(data, f, indent=2) # Read JSON with open("user.json", "r", encoding="utf-8") as f: loaded = json.load(f) print(loaded["user"], loaded["scores"])
json.dump() writes to a file; json.dumps() returns a string. The same pattern applies to json.load() (from file) and json.loads() (from string).🔢 Lesson 9 — Binary Files
Images, audio, and any non‑text data
🎵 Working with 'rb' and 'wb'
Binary mode reads and writes raw bytes without interpreting them as text. Use binary mode for images, audio, PDFs, and any file that isn’t UTF‑8 encoded text.
# Writing binary data with open("data.bin", "wb") as f: f.write(b"\x00\x01\x02\x03") # Reading binary data with open("data.bin", "rb") as f: raw = f.read() print(f"Read {len(raw)} bytes: {raw}")
UnicodeDecodeError for most binary content.🛡️ Lesson 10 — Error Handling in File Operations
What happens when files are missing, permissions fail, or disks fill up
⚠️ Handling FileNotFoundError and More
Real programs must anticipate failure. The most common file‑related exceptions are:
FileNotFoundError— the file doesn’t exist.PermissionError— the process lacks rights to read or write.IsADirectoryError— a directory was passed instead of a file.UnicodeDecodeError— the file encoding doesn’t match.
try: with open("missing.txt", "r", encoding="utf-8") as f: content = f.read() except FileNotFoundError: print("The file does not exist.") except PermissionError: print("You do not have permission to read this file.") except UnicodeDecodeError: print("The file encoding is not UTF-8.") else: print("File read successfully.") finally: print("Execution finished.")
Exception in file code — it hides bugs and makes debugging harder.✅ Lesson 11 — Safe File Handling Patterns
Best practices for robust, production‑ready file I/O
🏗️ Putting It All Together
Safe file handling combines several habits:
- Always use
withto guarantee closure. - Explicitly specify
encoding="utf-8"for text files. - Use
pathlib.Pathfor cross‑platform path building. - Check
.exists()before reading, or handleFileNotFoundError. - Never modify a file in‑place while reading it — write to a temporary file and rename.
from pathlib import Path import tempfile import os def safe_update_file(path: Path, new_content: str) -> None: # Write to a temporary file, then replace atomically tmp_fd, tmp_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp") try: with os.fdopen(tmp_fd, "w", encoding="utf-8") as f: f.write(new_content) os.replace(tmp_path, path) # atomic on POSIX and Windows except Exception: os.unlink(tmp_path) raise # Usage p = Path("notes.txt") if p.exists(): print("File exists, updating safely.") safe_update_file(p, "Updated content.") print("Done.")
pathlib, exception handling, and atomic operations into one robust helper function.❓ Frequently Asked Questions
Common questions about file handling in Python
'w' opens a file for writing and truncates it — all existing content is erased. 'a' opens a file for appending, so new data is written at the end without removing existing content. Use 'a' for logs and incremental records.
The default encoding depends on your operating system. On Windows it's often cp1252, which fails for many Unicode characters. Specifying UTF‑8 explicitly ensures the same behaviour on every platform and prevents UnicodeDecodeError or corrupted output.
You can open a file with mode 'r+' (read and write) but the position and buffering can be tricky. The safer pattern is to read the entire content, close the file, then reopen in write mode — or use the temporary‑file‑and‑replace approach shown in Lesson 11.
On Windows, the default newline translation can insert extra blank lines when writing CSV data. Passing newline='' disables that translation, letting the csv module handle line endings correctly. It's a recommended practice in the official documentation.
Use pathlib.Path.unlink() or os.remove(). Both raise FileNotFoundError if the file doesn't exist. For directories, use Path.rmdir() (empty only) or shutil.rmtree() (entire tree).