How programs read and write data that survives after they stop running — paths, the file system, opening and closing files, and the with statement
Every program you have written so far forgets everything the moment it stops running. Variables disappear. Results are gone. The next time you run the program, you start from zero. Files change that. A file is data that lives on disk. When your program reads a file, it pulls stored information into memory. When it writes a file, it saves information that will still exist after the program ends, after the computer restarts, and after months have passed.
This chapter teaches you how to work with text files in Python. You will learn how the file system is organized, how paths tell Python where files live, how to open and close files safely, how to read text from files, how to write and append text to files, and how to handle common file errors. By the end of this chapter, your programs will be able to store and retrieve real data.
A grade-tracking program needs to remember every student's score even after it closes. A note-taking app needs your notes to still be there tomorrow. A weather station logs a temperature reading every hour, forever. None of these programs can rely on variables alone — variables vanish when the program ends. What do all three have in common? Each one depends on files: data written to disk that outlives the program that created it. This chapter shows you exactly how that works in Python.
By the end of this chapter, you should be able to:
open() and explain the purpose of each file mode.with statement to open files safely and explain why it is preferred over manual opening and closing..read(), .readline(), .readlines(), and direct iteration..write() and .writelines().FileNotFoundError.open(), file modes, file objects, closing files, and the safer with statement.read(), .readline(), .readlines(), iterating directly over a file, and a worked line/word counting example.write(), .writelines(), overwriting behavior, and a worked report-generation examplewith StatementContext managers; why with guarantees a file is closed; opening multiple files at onceFileNotFoundError, PermissionError, IsADirectoryError, silent logic errors, and a preview of try/exceptThink about programs you use every day. A text editor saves your writing. A spreadsheet saves your numbers. A chat app stores your message history. A game saves your progress. Every one of these programs uses files.
Python programs use files for many of the same reasons:
Consider a grade-tracking program. Without files, the teacher must retype all the student grades every time she runs the program. With files, the grades are stored on disk. The program reads them at startup and writes updated grades when it finishes.
The shift from no-file programs to file-using programs is a big step. It means your programs can accumulate information over time, interact with data that already exists, and produce lasting results.
Every program in Chapters 1 through 20 lost its data the moment it stopped. Files are the first tool in this book that breaks that limitation. Pay attention to how different this feels — it is a genuine turning point in what your programs can do.
Before writing any Python, you need a clear picture of how files are organized.
Your computer stores files in a file system — a hierarchical structure of folders (also called directories) and files. A folder can contain files and other folders. This nesting creates a tree of locations from a starting point called the root.
On Windows, the root of a drive looks like C:\. On macOS and Linux, the root looks like /. Everything else branches from there.
A path is a string that describes where a file or folder is located. Think of it as the address of a file on disk.
C:\Users\Rosa\Documents\grades.txt (Windows)
/home/rosa/documents/grades.txt (macOS / Linux)
Paths use separators between each folder name. Windows uses backslashes (\). macOS and Linux use forward slashes (/). Python on Windows also accepts forward slashes in many situations, and pathlib (covered in Appendix J) handles this portably.
The file name is the last part of the path. It typically has a base name and an extension that suggests what type the file is:
grades.txt base name: grades extension: .txt
notes.md base name: notes extension: .md
report.csv base name: report extension: .csv
The extension is a convention. Python does not enforce what you put inside a .txt file, but good naming helps you and others understand the file's purpose.
There are two ways to describe a file's location.
An absolute path gives the full address from the root. It works regardless of where your program is running.
C:\Users\Rosa\Documents\grades.txt
/home/rosa/documents/grades.txt
Absolute paths are unambiguous. But they are fragile — if you move your project to a different computer or folder, the absolute path breaks.
A relative path describes a location relative to the current working directory — the folder Python considers its base when it runs.
If Python is running in /home/rosa/projects/grades/, then:
grades.txt means /home/rosa/projects/grades/grades.txt
data/scores.csv means /home/rosa/projects/grades/data/scores.csv
../notes.txt means /home/rosa/projects/notes.txt
The .. means "go up one folder level."
For beginner projects, relative paths are usually the right choice. Put your program file and your data files in the same folder, and use just the file name as the path. This keeps the project portable and simple.
# If grades.txt is in the same folder as your program:
filename = "grades.txt"
If you need subfolders:
filename = "data/grades.txt"
When you start a project, organize it before writing code. Create a folder for the project. Put your .py files and your data files in sensible locations inside that folder. Knowing where your files are before you code will save you frustration.
Before Python can read or write a file, you must open it. Opening a file creates a connection between your program and the file on disk. Python calls this connection a file object.
The built-in open() function opens a file and returns a file object.
file = open("notes.txt", "r")
The first argument is the path. The second argument is the mode — a string that tells Python what you want to do with the file.
| Mode | Meaning |
|---|---|
"r" | Read. Opens an existing file for reading. Error if the file does not exist. |
"w" | Write. Creates a new file or overwrites an existing one. |
"a" | Append. Creates a new file or adds to the end of an existing one. |
"x" | Exclusive creation. Creates a new file. Error if it already exists. |
If you omit the mode, Python defaults to "r".
When you are done with a file, you should close it. Closing releases the connection and ensures that any data waiting to be written actually gets saved to disk.
file = open("notes.txt", "r")
# ... do work with the file ...
file.close()
Forgetting to close a file can cause problems: data may not be saved, other programs may be locked out of the file, and your program may consume resources unnecessarily.
Python gives you a cleaner and safer way to handle files: the with statement. It automatically closes the file when the block ends, even if an error occurs inside the block.
with open("notes.txt", "r") as file:
# work with file here
pass
# file is automatically closed here
The as file part gives the file object a name inside the block. Once the with block ends, the file is closed. You do not have to call file.close() yourself.
Always use the with statement when working with files. It is safer, cleaner, and shorter than manually opening and closing.
Once a file is open for reading, Python gives you several ways to get the content.
The .read() method returns the entire file as one string.
with open("message.txt", "r") as file:
content = file.read()
print(content)
This is simple and works well for small files. For very large files, loading everything into one string uses a lot of memory.
Hello, Rosa.
Your report is ready.
Please review it by Friday.
The .readline() method reads one line and moves a pointer forward. The next call reads the next line.
with open("message.txt", "r") as file:
line1 = file.readline()
line2 = file.readline()
print(line1)
print(line2)
Notice the blank lines. Each line from the file already ends with a newline character (\n), and print() adds another. You can strip the trailing newline with .rstrip():
line1 = file.readline().rstrip("\n")
Or more commonly, .strip() removes all leading and trailing whitespace:
line1 = file.readline().strip()
The .readlines() method returns a list where each element is one line from the file, including the newline character.
with open("message.txt", "r") as file:
lines = file.readlines()
print(lines)
This is useful when you want to work with all the lines as a list.
The most Pythonic way to read a file line by line is to iterate over the file object directly using a for loop. This is memory-efficient and clean.
with open("message.txt", "r") as file:
for line in file:
print(line.strip())
For reading files line by line, use a for loop directly over the file object. It is readable, memory-efficient, and handles large files gracefully.
line_count = 0
word_count = 0
with open("message.txt", "r") as file:
for line in file:
line = line.strip()
line_count += 1
words = line.split()
word_count += len(words)
print("Lines:", line_count)
print("Words:", word_count)
line is "Hello, Rosa." → line_count becomes 1. words is ["Hello,", "Rosa."] → word_count becomes 2.line is "Your report is ready." → line_count becomes 2. words is ["Your", "report", "is", "ready."] → word_count becomes 6.line is "Please review it by Friday." → line_count becomes 3. words is ["Please", "review", "it", "by", "Friday."] → word_count becomes 11.Opening a file in "w" mode lets you write to it.
Opening an existing file in "w" mode destroys everything already in the file. The old content is gone immediately when you call open() with mode "w". There is no undo.
The .write() method writes a string to the file. It does not automatically add a newline — you must include \n yourself if you want line breaks.
with open("output.txt", "w") as file:
file.write("Line one\n")
file.write("Line two\n")
file.write("Line three\n")
Line one
Line two
Line three
The .writelines() method writes all strings in a list. Like .write(), it does not add newlines automatically.
lines = ["apple\n", "banana\n", "cherry\n"]
with open("fruits.txt", "w") as file:
file.writelines(lines)
apple
banana
cherry
names = ["Aisha", "Ben", "Clara"]
scores = [88, 74, 95]
with open("report.txt", "w") as file:
file.write("Student Score Report\n")
file.write("====================\n")
for i in range(len(names)):
line = f"{names[i]}: {scores[i]}\n"
file.write(line)
file.write("====================\n")
average = sum(scores) / len(scores)
file.write(f"Class average: {average:.1f}\n")
print("Report saved to report.txt")
When you open a file in "w" mode, you erase what was there. When you open a file in "a" (append) mode, Python adds new content at the end of the existing file. If the file does not exist, Python creates it.
with open("log.txt", "a") as file:
file.write("Program started at 9:00 AM\n")
Run this several times. Each run adds another line to log.txt. The old content is preserved.
import datetime
def log_event(message):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
entry = f"[{timestamp}] {message}\n"
with open("events.log", "a") as file:
file.write(entry)
log_event("Program started")
log_event("User entered a name")
log_event("Program finished")
events.log. The function opens the file, writes one entry, and closes it again — three separate with blocks in total.events.log contains six lines in total, each with its own timestamp, in the order the events occurred.This pattern — opening in append mode, writing a timestamped entry, closing — is the basis of log files in almost every real program.
Use "w" when: You want to create or completely replace a file.
Use "a" when: You want to add to an existing file without losing what is already there.
The with statement is Python's way of managing resources that need to be opened and closed properly. It is called a context manager.
with open("data.txt", "r") as file:
content = file.read()
What happens step by step:
open("data.txt", "r") and gets a file object.file.file.close().This guarantees the file is always closed, even if an exception occurs inside the block.
Compare the two approaches:
file = open("data.txt", "r")
content = file.read()
file.close() # If an error happens above, this never runs
with open("data.txt", "r") as file:
content = file.read()
# file is always closed here
The with version is not just shorter. It is genuinely safer. Make it a habit from the start.
You can open two files in one with statement using a comma:
with open("input.txt", "r") as infile, open("output.txt", "w") as outfile:
for line in infile:
outfile.write(line.upper())
This reads every line from input.txt and writes it in uppercase to output.txt.
Text files are sequences of characters. Lines end with a newline character, written in Python as \n. Understanding newlines prevents a lot of confusing bugs.
When you read a line from a file, the newline character is included at the end:
with open("items.txt", "r") as file:
line = file.readline()
print(repr(line))
If items.txt contains apple, the output will be:
The repr() function shows you the string exactly as Python stores it, including invisible characters. This is a useful debugging trick.
Most of the time you want to remove the trailing newline before working with the line. Use .strip() or .rstrip("\n"):
with open("items.txt", "r") as file:
for line in file:
item = line.strip()
print(item)
.strip() removes all leading and trailing whitespace — spaces, tabs, and newlines. This is usually what you want.
When you write to a file, Python does not add newlines for you. If you forget \n, all your output runs together on one line:
with open("test.txt", "w") as file:
file.write("first")
file.write("second")
file.write("third")
That is one long line, not three separate lines. Add \n to each:
with open("test.txt", "w") as file:
file.write("first\n")
file.write("second\n")
file.write("third\n")
Windows uses \r\n (carriage return + newline) as the line ending. macOS and Linux use \n. Python's open() handles this automatically in text mode — it converts \r\n to \n when reading and writes the platform-appropriate ending when writing. You do not usually need to think about this, but it is good to know if you ever see mysterious \r characters in your data.
File operations can fail in many ways. Understanding the common errors helps you debug quickly and design programs that handle problems gracefully.
This is the most common file error. It happens when you try to open a file for reading and the file does not exist at the path you specified.
with open("missing.txt", "r") as file:
content = file.read()
Common causes:
Debugging approach: Print the path before opening to confirm it is what you expect. Use os.path.exists() to check.
import os
path = "missing.txt"
print("File exists:", os.path.exists(path))
This happens when your program tries to open a file it is not allowed to access — for example, a system file, a file owned by another user, or a file that another program has locked.
This is less common in student projects but can appear if you try to write to a read-only location or a protected system directory.
This happens when you pass a folder path where Python expects a file path.
with open("my_folder", "r") as file:
pass
One tricky error is not an exception at all. If you open a file in "r" mode and try to call .write(), you get an error. But sometimes students open in "r" mode and then wonder why their data was never saved — they forgot that "r" is read-only.
with open("output.txt", "r") as file:
file.write("hello\n") # io.UnsupportedOperation: not writable
This is a logic error, not a Python exception. If you mean to add to a file but open it with "w" instead of "a", the old content is silently erased.
# Bug: this erases the file each time instead of adding to it
with open("log.txt", "w") as file:
file.write("New entry\n")
Fix: use "a" mode when you want to preserve existing content.
You will learn exception handling fully in Chapter 23, but here is a preview of how to handle FileNotFoundError safely:
filename = "notes.txt"
try:
with open(filename, "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print(f"Error: the file '{filename}' was not found.")
print("Please check the file name and location.")
This lets your program continue gracefully instead of crashing when a file is missing.
Chapter 23 covers try, except, else, and finally in full detail, including how to handle multiple kinds of file and input errors in one program.
open() creates a file object using a path and a mode: "r" for reading, "w" for writing (overwriting), "a" for appending. Files should always be closed; the with statement does this automatically and safely..read() returns the whole file as one string, .readline() reads one line at a time, and .readlines() returns a list of lines. Iterating directly over a file object with a for loop is the cleanest, most memory-efficient way to process a file line by line..write() writes one string, and .writelines() writes a list of strings. Neither adds newlines automatically. Opening an existing file in "w" mode erases its previous contents."a") adds new content to the end of a file without destroying what is already there, which makes it the right choice for log files and other accumulating records.with statement is a context manager that opens a file, runs a block of code, and guarantees the file is closed afterward — even if an error occurs. It can open more than one file at a time using commas.\n. Reading methods preserve this character, so it is usually stripped with .strip(). Writing methods never add it, so it must be included explicitly.FileNotFoundError is the most common file error, typically caused by a wrong path or misspelled name. PermissionError and IsADirectoryError occur less often. Logic errors — like using "w" when you meant "a" — fail silently and are caught only by careful checking. Chapter 23 introduces try/except to handle these errors gracefully.open() that represents the connection to a file.open() that specifies how the file will be used ("r", "w", "a").Read each program carefully. Answer the questions without running the code.
with open("greeting.txt", "w") as file:
file.write("Hello\n")
file.write("World\n")
greeting.txt contain after this program runs?greeting.txt already contained the text "Goodbye\n" before this program ran, what will it contain afterward?with open("data.txt", "r") as file:
for line in file:
print(line.strip())
Assume data.txt contains:
apple
banana
cherry
line contain on the second iteration, before .strip() is applied?total = 0
count = 0
with open("scores.txt", "r") as file:
for line in file:
score = int(line.strip())
total += score
count += 1
if count > 0:
print(f"Average: {total / count:.1f}")
else:
print("No scores found.")
Assume scores.txt contains:
80
90
70
100
total and count after each iteration.scores.txt were empty?with open("notes.txt", "a") as file:
file.write("Meeting at 3pm\n")
with open("notes.txt", "a") as file:
file.write("Call Rosa\n")
notes.txt was empty before, what does it contain after both with blocks run?notes.txt contain?Trace each program by tracking variables after each significant step.
Assume words.txt contains:
cat
dog
fish
result = []
with open("words.txt", "r") as file:
for line in file:
word = line.strip()
result.append(word.upper())
print(result)
Trace the contents of result after each iteration. What does the program print?
lines_written = 0
items = ["pen", "notebook", "eraser"]
with open("supplies.txt", "w") as file:
for item in items:
file.write(item + "\n")
lines_written += 1
print(f"Wrote {lines_written} lines.")
Trace lines_written after each iteration. What does the program print? What does supplies.txt contain?
with open("log.txt", "w") as file:
file.write("Start\n")
with open("log.txt", "a") as file:
file.write("Middle\n")
with open("log.txt", "a") as file:
file.write("End\n")
with open("log.txt", "r") as file:
content = file.read()
print(content)
Trace what log.txt contains after each with block. What does the program print at the end?
For each program, predict exactly what is printed to the screen. If the program causes an error, identify the error and explain why.
Assume hello.txt contains exactly: Hello, world! (no newline at the end)
with open("hello.txt", "r") as file:
text = file.read()
print(len(text))
print(text.upper())
with open("numbers.txt", "w") as file:
for n in range(1, 5):
file.write(str(n))
with open("numbers.txt", "r") as file:
print(file.read())
Assume ghost.txt does not exist.
with open("ghost.txt", "r") as file:
print(file.read())
lines = ["one\n", "two\n", "three\n"]
with open("count.txt", "w") as file:
file.writelines(lines)
with open("count.txt", "r") as file:
data = file.readlines()
print(len(data))
print(data[1])
Each program below contains a bug or design problem. Identify the issue and write a corrected version.
file = open("output.txt", "w")
file.write("Hello\n")
file.write("World\n")
What is the problem? What is the risk if this code runs inside a larger program that might crash?
with open("journal.txt", "w") as file:
file.write("Day 1: Arrived safely.\n")
with open("journal.txt", "w") as file:
file.write("Day 2: Explored the city.\n")
with open("journal.txt", "r") as file:
print(file.read())
The student expected to see both days printed. What went wrong? How do you fix it?
with open("prices.txt", "r") as file:
for line in file:
price = float(line)
print(f"${price:.2f}")
Assume prices.txt contains:
3.99
12.50
7.00
The program crashes. What is the likely error? How do you fix it?
with open("report.txt", "w") as file:
file.write("Name: Aisha")
file.write("Score: 95")
file.write("Grade: A")
The program runs without error, but the file does not look right. What is wrong? Write the corrected version.
filename = "data.txt"
with open(filename, "r") as file:
lines = file.readlines()
with open(filename, "w") as file:
for line in lines:
file.write(line)
This program reads a file and writes it back. It appears to work, but there is a potential danger. What is it? Under what circumstances could this destroy data?
Write a complete Python program for each task. Test your program before submitting it.
words.txt. After saving, open the file and print all the words back.count_lines(filename) that takes a file name as a parameter and returns the number of lines in the file. If the file does not exist, the function should return -1. Test your function with at least two different files.original.txt contains first, second, third on separate lines, then reversed.txt should contain third, second, first.log(message) that appends a line to a file called program.log. The line should contain the message followed by a newline. Call the function at least four times with different messages, then read and print the entire log file.numbers.txt where each line contains one integer. The program should compute and print the minimum, maximum, sum, and average of all the numbers. Handle the case where the file is empty.Build a small console program that manages a personal notes file and a log file.
When the program starts, it should append a timestamped entry to activity.log saying the program started, using the format [YYYY-MM-DD HH:MM:SS] Program started.
=== Notes Program ===
1. View all notes
2. Add a note
3. Clear all notes
4. Quit
notes.txt. If the file does not exist, print a friendly message saying no notes exist yet.notes.txt. Also log the action to activity.log: [timestamp] Note added.notes.txt in "w" mode and write nothing, effectively emptying it. Ask for confirmation before clearing. Log the action.[timestamp] Program ended. and exit the program.with statements for every file operation.notes.txt does not exist when the user tries to view notes.log_event(message), view_notes(), add_note(text), clear_notes(), show_menu(), and main().Add the date and time to each note automatically when it is added. Add a "Search notes" option that prints only lines containing a given word. Format the log file entries so the program name is included: [timestamp] [notes_program] Note added. Let the user choose a custom notes file name at startup.
=== Notes Program ===
1. View all notes
2. Add a note
3. Clear all notes
4. Quit
> 2
Enter your note: Buy milk and eggs.
Note saved.
=== Notes Program ===
1. View all notes
2. Add a note
3. Clear all notes
4. Quit
> 1
--- Your Notes ---
Buy milk and eggs.
------------------
=== Notes Program ===
...
> 4
Goodbye.
These questions draw on earlier chapters. Keep your earlier skills sharp.
words_in_list(filename) that reads a text file (one word per line) and returns a list of all the words, stripped of whitespace. If the file does not exist, return an empty list.times_table.txt. Each row should look like 1 x 1 = 1 through 5 x 5 = 25.inventory.txt has entries in this format, one per line: pencil,50. Write a program that reads the file and prints each item and its quantity, then prints the total number of items across all categories.save_list(items, filename) that takes a list of strings and writes each item on its own line to the given file. Write a second function called load_list(filename) that reads the file and returns a list of strings (stripped). Demonstrate that saving a list and then loading it gives back the same list.Chapter 21 taught you to read and write plain text files — raw lines of characters. Many real programs work with structured table data: rows and columns, like a spreadsheet. The most common format for this kind of data is the CSV file (comma-separated values). Chapter 22 introduces CSV files. You will learn how rows and columns are stored, how to read CSV files with Python's csv module, how to write CSV files, how to filter rows, how to summarize columns, and how to clean table data. The skills in Chapter 22 connect directly to data analysis, spreadsheets, databases, and practical automation.