Reading and writing text files
Open a text file with with open and encoding utf-8, read it whole or line by line, write and append, and handle a missing file.
What you will learn
- Open a file with with open(..., encoding="utf-8") and read it with read() and by looping over lines
- Write and append text with the "w" and "a" modes
- Explain why with closes the file for you
- Handle a missing file with FileNotFoundError
On this page
Until now every program you wrote forgot everything the moment it ended. Files fix that. A program can write results to disk and read them back the next time it runs, or read data someone else prepared. This lesson covers plain text files: the simplest and most useful kind.
The examples read a small file called notes.txt, which sits in the same folder as the scripts. Its content is three lines:
Ada Lovelace wrote the first program.Café au lait costs 3 euros.Pi is roughly 3.14159.Python looks for a file name like notes.txt in the current working directory, which is the folder your terminal is in when you run the script. If you get a FileNotFoundError, check where your terminal is first.
Open, read, close
The standard way to read a whole file is with open(...).
with open("notes.txt", encoding="utf-8") as file: text = file.read()
print(text)print(len(text), "characters")print(repr(text[:12]))Ada Lovelace wrote the first program.Café au lait costs 3 euros.Pi is roughly 3.14159.
89 characters'Ada Lovelace'Three things are going on:
open("notes.txt", encoding="utf-8")opens the file for reading, since reading is the default mode. It returns a file object.with ... as file:gives that object the namefileand guarantees the file is closed when the indented block ends, even if an error happens inside. This is the job afinallyblock would do by hand, as the try and except lesson mentioned.file.read()returns the whole content as one string, including the newline character\nat the end of each line. That is whyprint(text)ends with a blank line: the text already ends in a newline andprintadds another.
The encoding argument
A file stores bytes, and the encoding says how bytes map to characters. The Unicode and bytes lesson explained this. The é in Café takes two bytes in UTF-8, so reading it with the wrong encoding gives garbled text such as Café.
Requires version 3.15 or later UTF-8 is now the default for open() on every platform (PEP 686). Before 3.15 the default came from the operating system, and on many Windows machines it was not UTF-8, so the same program could read a file correctly on one computer and garble it on another. You can still switch the default off with PYTHONUTF8=0 or -X utf8=0.
Even so, keep writing encoding="utf-8". It costs nothing, your code then behaves the same on Python 3.14 and earlier, and the reader can see the intention. The examples in this course always include it.
Reading line by line
A file object is iterable: looping over it gives one line at a time. For big files, this is better than read(), because it does not load everything at once.
with open("notes.txt", encoding="utf-8") as file: for number, line in enumerate(file, start=1): print(number, line.rstrip("\n"))1 Ada Lovelace wrote the first program.2 Café au lait costs 3 euros.3 Pi is roughly 3.14159.enumerate(file, start=1) numbers the lines from 1, as in the for loops lesson. rstrip("\n") removes the newline from the end of each line, so print does not add a second one. Two related methods are worth knowing: file.readline() reads one line, and file.readlines() returns a list of all lines.
Writing and appending
The second argument of open is the mode:
| Mode | Meaning |
|---|---|
"r" |
Read. This is the default. The file must exist. |
"w" |
Write. Creates the file, or erases it if it already exists. |
"a" |
Append. Adds to the end, creating the file if needed. |
import os
with open("shopping.txt", "w", encoding="utf-8") as file: file.write("milk\n") file.write("café crème\n") print("bread", file=file)
with open("shopping.txt", "a", encoding="utf-8") as file: file.write("eggs\n")
with open("shopping.txt", encoding="utf-8") as file: items = file.read().splitlines()
print(items)print(len("café crème"), "characters,", len("café crème".encode("utf-8")), "bytes")os.remove("shopping.txt")['milk', 'café crème', 'bread', 'eggs']10 characters, 12 bytesfile.write writes exactly the string you give it, so you must add \n yourself. Passing file=file to print is a neat alternative: print adds the newline for you. The second with block uses mode "a", so eggs goes at the end rather than replacing the content. splitlines() turns the text back into a list without the newline characters. The final lines show that the two accented letters each take two bytes in UTF-8, so ten characters occupy twelve bytes on disk. os.remove deletes the file again, so you can rerun the example. It comes from the os module, brought in with import as in the previous lesson.
When the file is missing
Opening a file that does not exist raises FileNotFoundError. It is an error you can expect, so it is a good fit for try and except.
def count_lines(filename): try: with open(filename, encoding="utf-8") as file: return len(file.readlines()) except FileNotFoundError: print("Cannot find", filename) return 0
print(count_lines("notes.txt"))print(count_lines("nothing_here.txt"))3Cannot find nothing_here.txt0The with sits inside the try: if open fails, there is no file to close, and the handler runs. A return inside with is fine, because the file still closes on the way out.
Common pitfalls
- Forgetting
encoding="utf-8". On Python 3.14 and earlier this can corrupt accented letters on Windows. Always include it. - Using
"w"when you meant"a". It silently erases the file. - Forgetting the newline.
write("milk")followed bywrite("eggs")producesmilkeggson one line. - Reading a file twice with one
read(). Afterread()the position is at the end, so a secondread()returns"". Open the file again, or loop once. - Wrong working directory.
FileNotFoundErrorfor a file you can see usually means the terminal is in another folder. - Leaving out
with. A file you open by hand must be closed by hand, and an error in between leaves it open.
Read notes.txt (the three lines shown at the top) into a list of lines without their newlines, and print the number of lines, the longest line and its length. Expected output:
Lines: 3Longest: Ada Lovelace wrote the first program.Length: 37Hint: max(lines, key=len) returns the longest item.
Show solution
with open("notes.txt", encoding="utf-8") as file: lines = [line.rstrip("\n") for line in file]
longest = max(lines, key=len)print("Lines:", len(lines))print("Longest:", longest)print("Length:", len(longest))Lines: 3Longest: Ada Lovelace wrote the first program.Length: 37The comprehension from the comprehensions lesson works on the file object because it is iterable. The key=len argument comes from the lambdas lesson.
Write the squares of 1 to 5, one per line, to a file called squares.txt. Then read the file back, convert each line to an integer and print the total. Delete the file at the end. Expected output:
Total: 55Show solution
import os
with open("squares.txt", "w", encoding="utf-8") as file: for n in range(1, 6): file.write(f"{n * n}\n")
total = 0with open("squares.txt", encoding="utf-8") as file: for line in file: total += int(line)
print("Total:", total)os.remove("squares.txt")Total: 55A file holds text only, so the numbers were written as text and converted back with int. int("4\n") works because int ignores the surrounding whitespace, including the newline. The sum is 1 + 4 + 9 + 16 + 25 = 55.
This program is meant to print the three lines of notes.txt twice, but the second pass prints nothing. Why? Fix it so that each line is printed twice, once per pass.
with open("notes.txt", encoding="utf-8") as file: for line in file: print("first pass:", line.rstrip("\n")) for line in file: print("second pass:", line.rstrip("\n"))Show solution
The first loop reads the file to the end. The file object remembers its position, so the second loop starts at the end and finds no lines. Fix it by reading the lines into a list once, then looping over the list twice:
with open("notes.txt", encoding="utf-8") as file: lines = file.read().splitlines()
for line in lines: print("first pass:", line)for line in lines: print("second pass:", line)A list can be looped over any number of times, a file object only once.
Summary
- Use
with open(name, encoding="utf-8") as file:. The block closes the file for you, even after an error. file.read()gives the whole text, looping over the file gives lines, and"w"and"a"write and append.writedoes not add a newline.- Requires version 3.15 or later UTF-8 is the default encoding for
open(), but writeencoding="utf-8"anyway so your code behaves the same on older versions. - A missing file raises
FileNotFoundError, which you can catch withtryandexcept.
Next: debugging habits.