File I/O (Input/Output) is how Python reads data from files and writes data to them. It is one of the most fundamental ways a program interacts with the outside world — reading configuration files, processing data, logging output, saving results, and working with structured formats like CSV and JSON. Python's built-in tools for file handling are clean and straightforward.
Opening a File — open()
The open() function is the entry point for all file operations. It returns a file object that you use to read or write:
file = open("filename.txt", "r")
File modes
The second argument to open() is the mode — it determines what you can do with the file:
| Mode | Meaning |
|---|---|
"r" | Read (default). File must exist. |
"w" | Write. Creates the file if it doesn't exist; overwrites if it does. |
"a" | Append. Creates if needed; adds to the end without overwriting. |
"x" | Exclusive creation. Fails if the file already exists. |
"r+" | Read and write. File must exist. |
"b" | Binary mode — add to another mode: "rb", "wb". |
"t" | Text mode (default) — add to another mode: "rt", "wt". |
The with Statement — Always Use It
The recommended way to work with files is the with statement. It automatically closes the file when the block exits — even if an error occurs:
with open("data.txt", "r") as file:
content = file.read()
# File is automatically closed here
Without with, you must close the file manually — and if an exception occurs before file.close(), the file stays open, which can cause data loss or resource leaks:
# Without with — not recommended
file = open("data.txt", "r")
content = file.read()
file.close() # Easy to forget, skipped on exceptions
Always use with open(...).
Reading Files
Read the entire file — .read()
with open("data.txt", "r") as file:
content = file.read()
print(content)
Returns the entire file as a single string.
Read line by line — .readline()
with open("data.txt", "r") as file:
line = file.readline() # Reads one line at a time
while line:
print(line, end="") # end="" avoids double newlines
line = file.readline()
Read all lines into a list — .readlines()
with open("data.txt", "r") as file:
lines = file.readlines() # ["line 1\n", "line 2\n", "line 3\n"]
for line in lines:
print(line.strip()) # strip() removes the trailing \n
Iterate directly over the file — most Pythonic
with open("data.txt", "r") as file:
for line in file:
print(line.strip())
Iterating directly over the file object is memory-efficient — it reads one line at a time without loading the entire file into memory. This is the preferred approach for large files.
Writing Files
Write to a file — "w" mode
with open("output.txt", "w") as file:
file.write("Hello, World!\n")
file.write("Second line\n")
⚠️ Opening a file in
"w"mode overwrites any existing content. The file is truncated to zero length when opened.
Write multiple lines at once — .writelines()
lines = ["Line one\n", "Line two\n", "Line three\n"]
with open("output.txt", "w") as file:
file.writelines(lines)
Note that .writelines() does not add newlines automatically — include \n in each string.
Append to a file — "a" mode
with open("log.txt", "a") as file:
file.write("New log entry\n")
Opening in "a" mode adds content to the end of the file without touching what is already there.
Working with File Paths — pathlib
The pathlib module provides an object-oriented interface for file paths that is cleaner and more readable than string concatenation:
from pathlib import Path
# Building paths safely (works on Windows, macOS, and Linux)
base = Path("my_project")
data_file = base / "data" / "input.txt"
print(data_file) # my_project/data/input.txt
# Reading with pathlib
content = data_file.read_text()
# Writing with pathlib
output = Path("output.txt")
output.write_text("Hello from pathlib")
Useful pathlib operations
p = Path("my_project/data/input.txt")
p.exists() # True if the path exists
p.is_file() # True if it is a file
p.is_dir() # True if it is a directory
p.name # "input.txt" — filename with extension
p.stem # "input" — filename without extension
p.suffix # ".txt" — file extension
p.parent # Path("my_project/data") — parent directory
# Creating directories
Path("new_folder").mkdir(exist_ok=True)
Path("a/b/c").mkdir(parents=True, exist_ok=True) # Creates all parents
# Listing directory contents
for file in Path(".").iterdir():
print(file)
# Glob — find files matching a pattern
for txt_file in Path(".").glob("*.txt"):
print(txt_file)
Working with CSV Files
CSV (Comma-Separated Values) is one of the most common data formats. Python's csv module handles the parsing correctly — including quoted fields and commas within values:
Reading CSV
import csv
with open("users.csv", "r", newline="") as file:
reader = csv.reader(file)
header = next(reader) # Skip the header row
for row in reader:
print(row) # Each row is a list of strings
Reading CSV as dictionaries
import csv
with open("users.csv", "r", newline="") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["name"], row["age"]) # Access by column name
Writing CSV
import csv
users = [
["name", "age", "city"],
["Wariz", "20", "Lagos"],
["Ada", "25", "Abuja"]
]
with open("users.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(users)
Working with JSON Files
JSON is covered fully in the JSON topic. Here is the file-specific pattern:
import json
# Reading JSON from a file
with open("data.json", "r") as file:
data = json.load(file) # Parses JSON and returns a Python object
# Writing JSON to a file
user = {"name": "Wariz", "age": 20, "city": "Lagos"}
with open("data.json", "w") as file:
json.dump(user, file, indent=4) # indent for pretty-printing
Handling File Errors
File operations can fail — the file may not exist, permissions may be denied, or the disk may be full. Always handle these cases:
try:
with open("data.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("File does not exist.")
except PermissionError:
print("Permission denied.")
except OSError as e:
print(f"OS error: {e}")
Common file-related exceptions:
| Exception | When it occurs |
|---|---|
FileNotFoundError | File or directory does not exist |
PermissionError | Insufficient permissions to read or write |
FileExistsError | File already exists (raised by "x" mode) |
IsADirectoryError | Tried to open a directory as a file |
OSError | General OS-level error — catches all of the above |
Checking if a File Exists Before Opening
from pathlib import Path
path = Path("data.txt")
if path.exists():
content = path.read_text()
else:
print("File not found")
This avoids the exception entirely when you just need a conditional check.
Reading and Writing Binary Files
For non-text files — images, PDFs, compiled binaries — use binary mode:
# Reading a binary file
with open("image.png", "rb") as file:
data = file.read() # Returns bytes, not a string
# Writing a binary file
with open("copy.png", "wb") as file:
file.write(data)
Binary mode reads and writes raw bytes. Do not use text mode for binary files — it will corrupt them on Windows, which translates newline characters.
Summary
| Operation | Code |
|---|---|
| Open for reading | open("file.txt", "r") |
| Open for writing (overwrite) | open("file.txt", "w") |
| Open for appending | open("file.txt", "a") |
Always use with | with open(...) as f: |
| Read entire file | f.read() |
| Read line by line | for line in f: |
| Read all lines to list | f.readlines() |
| Write a string | f.write("text") |
| Write multiple lines | f.writelines(list) |
| Build a path safely | Path("folder") / "file.txt" |
| Check if file exists | Path("file").exists() |
| Read CSV with headers | csv.DictReader(f) |
| Read JSON from file | json.load(f) |
| Write JSON to file | json.dump(data, f, indent=4) |