Files and errorsChapter 79 of 114
File Handling
Open a file safely, in the right mode, with the right encoding.
Always use with
open() gives you a file object. with closes it for you, even if something goes wrong in the middle:
with open("notes.txt", "w") as f:
f.write("first line\n")
with open("notes.txt") as f:
print(f.read())Output
first line
Without with you have to remember f.close(), and an exception before it leaves the file open. There is no reason to write that version.
The modes
| Mode | Does | If the file exists |
|---|---|---|
"r" | read (the default) | reads it |
"w" | write | empties it first |
"a" | append | adds at the end |
"x" | create | raises FileExistsError |
"r+" | read and write | keeps the contents |
with open("log.txt", "w") as f:
f.write("one\n")
with open("log.txt", "a") as f:
f.write("two\n")
with open("log.txt") as f:
print(f.read())Output
one two
"x" is the safe way to say "only if it is not already there":
with open("once.txt", "x") as f:
f.write("created")
try:
with open("once.txt", "x") as f:
f.write("again")
except FileExistsError:
print("already exists, refused to overwrite")Output
already exists, refused to overwrite
Text and binary
By default a file is text: you read and write str, and Python decodes the bytes for you. Add "b" for raw bytes:
with open("data.bin", "wb") as f:
f.write(b"\x00\x01\x02")
with open("data.bin", "rb") as f:
print(f.read())Output
b'\x00\x01\x02'
Use binary mode for images, archives and anything that is not text.
Always name the encoding
with open("city.txt", "w", encoding="utf-8") as f:
f.write("München")
with open("city.txt", encoding="utf-8") as f:
print(f.read())Output
München
Without encoding=, Python uses the platform default, which is UTF-8 on Linux and macOS and historically something else on Windows. The same code then produces different results on different machines. Passing encoding="utf-8" costs nothing and removes a whole category of bug.
Missing files
try:
with open("not-there.txt") as f:
print(f.read())
except FileNotFoundError:
print("no such file")Output
no such file
Catching the error beats checking first: between a check and the open, the file could disappear.
pathlib
For paths, pathlib is clearer than string joining and works on every platform:
from pathlib import Path
p = Path("notes.txt")
p.write_text("written with pathlib\n", encoding="utf-8")
print(p.read_text(encoding="utf-8").strip())
print(p.name, p.suffix, p.stem)
print(p.exists())Output
written with pathlib notes.txt .txt notes True
Path("folder") / "file.txt" builds a path with the right separator, so you never have to think about backslashes.
from pathlib import Path
path = Path("data") / "reports" / "march.csv"
print(path.parts)
print(path.name, path.parent.name)Output
('data', 'reports', 'march.csv')
march.csv reportsPrinting the path itself would show forward slashes here and backslashes on Windows, which is the whole point: you write it once and pathlib renders it for whichever machine it runs on.
Test yourself
2 questionsWhat does opening a file in "w" mode do to an existing file?
Show the answer
Empties it immediately, before you write anything — Use "a" to append and "x" to refuse if the file already exists.
Why pass encoding="utf-8" explicitly?
Show the answer
The default depends on the platform, so the same code behaves differently — It costs nothing and removes a whole category of works-on-my-machine bug.
Read Files
Read a whole file, a line at a time, or lazily for a big one.