Reading and Writing Files

Writing Files


Learning Objectives

  • You can write text to a file using with and open(..., "w").
  • You can explain the difference between write mode ("w") and append mode ("a"), and why mixing them up can destroy data.
  • You can write multiple lines, including newlines, to a text file.
  • You can use .join() to build a delimited line from a list of pieces.
  • You can verify that what you wrote was actually written.

The previous chapter taught the program to read a file. This one teaches it to write one. The mechanics look similar — the same with/open shape, the same encoding habit — but writing comes with a danger that reading does not: you can accidentally destroy what was already there.

The Basic Form

The smallest useful example:

with open("greeting.txt", "w", encoding="utf-8") as f:
f.write("Hello from a file.\n")

Three small but important differences from the reading code:

  1. There is a second argument to open(...): the string "w". This is the mode, and "w" means “open the file for writing”. Without it, open(...) defaults to read mode and the write call fails.
  2. The work happens through f.write(...), not f.read(). .write(...) takes a string and writes it into the file.
  3. The "\n" at the end of the string is there on purpose. Read on for why.

After this code runs, greeting.txt exists in the working directory and contains the words Hello from a file. followed by a newline. If you run it twice, the file is the same; the second run just produces the same contents.

The Three Modes

There are three modes you will use in this course when working with text files:

ModeWhat it does
"r"Read. The default. The file must already exist.
"w"Write. Empties the file first. Creates the file if it does not exist.
"a"Append. Adds to the end of the file. Creates the file if it does not exist.

One of them, "w", comes with a surprise. Opening a file in write mode does not just “open it for writing” — it empties the file first. Whatever was in the file before is gone, instantly, the moment open(..., "w", ...) runs. There is no warning, no prompt, no recovery. If the file held a hundred sessions of carefully logged data and you run open("sessions.txt", "w", encoding="utf-8") by mistake, those sessions are now gone.

This matters enormously when files start holding the user’s actual data. Mixing up "w" and "a" is one of the most common ways to silently destroy data, and the danger is at its worst precisely because nothing goes wrong on the surface — the program runs, the new content is there, no error is raised. It just happens that everything else is gone.

The truncation rule: opening a file in "w" mode erases the file’s contents. If you want to preserve what is already there, use "a" instead.

Write mode

0 / 5 points

What happens as soon as an existing file is opened with open(path, "w", encoding="utf-8")?

Newlines Are Not Automatic

You may have noticed that print(...) adds a newline at the end of its output. Based on this, one might guess that f.write(...) does the same. It does not. .write(...) writes exactly what you give it, character by character. If you want each line to end with a newline, you have to include the \n yourself.

Compare:

print("Hello") # outputs: Hello\n
f.write("Hello") # writes: Hello (no newline)
f.write("Hello\n") # writes: Hello\n

The implication is that writing several lines means writing several strings, each with its own \n:

with open("subjects.txt", "w", encoding="utf-8") as f:
f.write("Math\n")
f.write("Physics\n")
f.write("History\n")

That produces a file with three lines, each terminated by a newline. Forgetting the newlines produces a file with one long line.

Write and newlines

0 / 5 points

Why does f.write("Done") behave differently from print("Done")?

Before writing a longer file, pause on the smallest version of the pattern: open the file, write one exact line, then the next exact line, then the next. If the newlines are in the right places here, the same habit carries over to loops and helper functions.

Write three checklist lines

0 / 5 points

Arrange the lines into a program that writes exactly these lines to tasks.txt:

1. Draft outline
2. Collect sources
3. Revise

Building a Line With .join()

The previous chapter introduced .split(), which breaks a string apart at a separator. Its mirror image is .join(), which builds a single string out of a list of pieces:

parts = ["Math", "45"]
line = ",".join(parts)
# line is now "Math,45"

The string "," is the separator. The list parts is what gets joined. The result is a single string with the separator placed between every adjacent pair.

Join Belongs to the String

Notice the join syntax: the separator is the thing the method is called on, and the list of pieces is the argument — ",".join(parts).

This might read strangely the first time, but think of this from the perspective of the string. The string “knows how to join” a list of pieces with itself as the separator. The list does not know how to join — it is just a list.

A natural use of .join(), given a parallel pair of lists of subjects and minutes, is to write each row as one comma-separated line:

subjects = ["Math", "Physics", "History"]
minutes_list = [45, 90, 30]
with open("sessions.txt", "w", encoding="utf-8") as f:
for subject, minutes in zip(subjects, minutes_list):
parts = [subject, str(minutes)]
line = ",".join(parts)
f.write(line + "\n")

Two new pieces appear here:

  • zip(subjects, minutes_list) walks the two lists in parallel, yielding ("Math", 45), then ("Physics", 90), then ("History", 30).
  • str(minutes) converts the number to a string before joining — .join() requires the pieces to be strings, and 45 is not.

If the fields are known in advance and small in number, the .join() approach is a bit more work than just writing an f-string:

subjects = ["Math", "Physics", "History"]
minutes_list = [45, 90, 30]
with open("sessions.txt", "w", encoding="utf-8") as f:
for subject, minutes in zip(subjects, minutes_list):
f.write(f"{subject},{minutes}\n")
Hidden Weakness

The .join() shown here works fine on clean data, but it has a hidden weakness (that split also has). If one of the fields contains the separator that is used in splitting and joining — say, a subject called “Math, Science” — then the line becomes ambiguous. You’ll learn how to handle this properly in the next chapter, with the csv module.

Verifying What You Wrote

After a write, it is a good habit to check that the file actually contains what you intended. There are two easy ways.

The first is to open the file in your editor and look at it directly.

The second is to read the file back in your code:

with open("subjects.txt", "w", encoding="utf-8") as f:
f.write("Math\n")
f.write("Physics\n")
with open("subjects.txt", encoding="utf-8") as f:
contents = f.read()
print(contents)

For longer programs, the read-back-and-check pattern can be turned into a small test using assert. This is the basis of what the CSV chapter calls a round-trip test: write something, read it back, check that what came out matches what went in.

Write checklist items

0 / 15 points

Define a function named write_checklist(path, tasks).

The function must write the tasks to the file at path, one task per line.

Requirements:

  • open the file with with open(path, "w", encoding="utf-8") as f,
  • use write mode "w" because the checklist should be replaced,
  • write each task exactly as it appears in the list,
  • write every task line with a newline character at the end,
  • and return None.

If tasks is:

["Draft outline", "Collect sources", "Revise"]

then the file must contain exactly:

Draft outline
Collect sources
Revise

including a newline after Revise.

At the bottom of the program, call:

write_checklist("checklist.txt", ["Draft outline", "Collect sources", "Revise"])

Then read checklist.txt back and print its contents.

Required output text:

Draft outline
Collect sources
Revise

The checklist exercise writes one simple file from a list. A slightly larger version of the same idea is to generate the line prefixes yourself: first just the numbers, then numbers with items, then cleaned items, and finally a small title line above the checklist.

Check Your Understanding

  1. What happens when you open a file with mode "w"? Why is this dangerous if the file already had data in it?
  2. What is the difference between "w" and "a" mode?
  3. Why does f.write("Hello") produce different output from print("Hello")?
  4. Rewrite the following line using an f-string:
    f.write(",".join([subject, str(minutes), date]) + "\n")
  5. You wrote some sessions to a file, but when you reopen the file in your editor you see only the most recent run’s data — all your previous sessions are gone. What is the most likely cause?

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0