Reading and writing files
Open, read, and write files safely using the with statement and handle missing files gracefully.
- Open files with open() in read, write, and append mode
- Read all text at once and iterate line by line
- Write text to a file
- Use the with statement to close files automatically
- Catch FileNotFoundError gracefully
Most real programs either read data from somewhere or record results somewhere.
Python's built-in open() function handles both sides cleanly, and the with
statement makes it nearly impossible to leave a file accidentally open.
Opening a file
open(path, mode) returns a file object. The mode controls what you can
do with it:
| Mode | Meaning |
|---|---|
'r' | Read (default). File must exist. |
'w' | Write. Creates the file; overwrites if it exists. |
'a' | Append. Creates the file; adds to the end if it exists. |
f = open("data.txt", "r")
content = f.read()
f.close() # you must close — or use with (see below)Forgetting close() leaks the file handle. The with statement fixes that.
The with statement — always use it
The with statement is a context manager — with open(...) guarantees the file is closed when the block exits, even if an
exception is raised:
with open("data.txt", "r") as f:
content = f.read()
# file is automatically closed hereThere is essentially no reason to call open() outside a with block in
production code. Make it a reflex.
Reading
Three patterns cover nearly every read case:
# 1. Read the entire file as one string
with open("notes.txt", "r") as f:
text = f.read()
# 2. Read into a list of lines (newline included)
with open("notes.txt", "r") as f:
lines = f.readlines()
# 3. Iterate line by line (memory-efficient for large files)
with open("notes.txt", "r") as f:
for line in f:
print(line.strip()) # strip() removes the trailing newlineIterating directly over the file object (pattern 3) is the most Pythonic and handles large files without loading everything into memory.
Writing
'w' mode creates or replaces the file; 'a' mode appends to it:
with open("output.txt", "w") as f:
f.write("Line one\n")
f.write("Line two\n")write() doesn't add a newline for you — include \n explicitly if you want
each call to produce a separate line.
To write multiple lines from a list, f.writelines(lines) writes each item
directly. As with write(), newlines are your responsibility — items are
joined without a separator.
Handling FileNotFoundError
If you open a file for reading and it doesn't exist, Python raises a
FileNotFoundError. Catch it specifically — the errors-and-exceptions lesson
showed why catching the precise type matters:
try:
with open("config.txt", "r") as f:
config = f.read()
except FileNotFoundError:
config = "" # treat a missing file as empty configA common variant: check whether a file exists before opening, using
pathlib.Path.exists() — but the try/except approach is often cleaner and
avoids a race condition between the check and the open.
A complete read-process-write example
This pattern — read lines, filter them, write results — appears constantly:
with open("data.txt", "r") as infile:
lines = [line.strip() for line in infile if line.strip()]
with open("filtered.txt", "w") as outfile:
for line in lines:
outfile.write(line + "\n")Opening a file with 'w' mode immediately truncates (empties) it, even
before you write anything. If you need to read and then rewrite the same
file, read it fully first, close it, then reopen with 'w'. Never open the
same file for both reading and writing in two separate handles at once.
Where to go next
You can now persist data to disk and load it back. Next: working with strings in depth — the richer set of methods you'll use when processing that text.