Skip to content

8 min read

American Corners

Reading and writing files

Data that outlives the program

Everything you have stored so far lives in memory, which means it vanishes the moment the program ends. A file is how you keep data on disk so it is still there tomorrow. To use one you follow three steps in order: open it, read from or write to it, then close it.

Opening a file needs two pieces of information, its name and a mode that says what you intend to do:

  • "r" for read, when you only want to look at what is there.
  • "w" for write, which starts a fresh, empty file. Beware: if the file already exists, this erases it completely.
  • "a" for append, which adds to the end of the file and keeps whatever was already inside.
notes = open("notes.txt", "w")
notes.write("First line\n")
notes.write("Second line\n")
notes.close()

Each write adds exactly the text you give it, so you include \n yourself wherever you want a new line. Forgetting to close() can leave your text half-written, so it must always happen.

Reading it back

Open the same file in read mode and pull the contents out. read() gives you the whole file as one string:

notes = open("notes.txt", "r")
contents = notes.read()
notes.close()
print(contents)

Often you would rather handle one line at a time, which a for loop over the file does neatly:

notes = open("notes.txt", "r")
for line in notes:
    print(line.strip())
notes.close()

The .strip() trims the trailing newline so you do not print blank lines between every one.

The safer way: a with-block

It is easy to forget the close(), and if an error strikes between opening and closing, the file may never close at all. Python gives you a cleaner pattern that closes the file for you automatically, even if something goes wrong. It is the with block, and it is the form you should prefer:

with open("notes.txt", "r") as notes:
    for line in notes:
        print(line.strip())

When the indented block ends, the file is closed for you. There is no close() to remember and no way to skip it by accident. Reach for with every time you open a file.

Learn more

Trying to read a file that does not exist raises a FileNotFoundError and stops the program. If there is a real chance the file is missing, catch that case and carry on rather than crashing:

try:
    with open("scores.txt", "r") as f:
        data = f.read()
except FileNotFoundError:
    data = ""
    print("No saved scores yet, starting fresh.")

If the file opens, data holds its contents. If it does not, you land in the except branch, set a sensible default, and the program keeps going.

A worked example: saving a list, loading it back

Suppose a small program collects a shopping list and should remember it between runs. Writing the list out is a loop, one item per line:

def save_list(items, filename):
    with open(filename, "w") as f:
        for item in items:
            f.write(item + "\n")

def load_list(filename):
    try:
        with open(filename, "r") as f:
            items = []
            for line in f:
                items.append(line.strip())
            return items
    except FileNotFoundError:
        return []

save_list(["bread", "olives", "cheese"], "shopping.txt")
print(load_list("shopping.txt"))   # ['bread', 'olives', 'cheese']

save_list writes each item followed by a newline. load_list reads the lines back, strips the newline off each one, and hands you a list again, returning an empty list if the file was never created. Together they let a list survive after the program closes, which is the whole reason files exist.

Warning

Opening an existing file with mode "w" erases it the instant you open it, before you write anything. When you mean to keep what is already there and add to it, use "a" instead. Mixing these two up is a classic way to lose data.

Prefer to see it explained? Here is the recorded Code for Albania lecture on this topic.

Check yourself

  1. What is the practical difference between opening a file with mode "w" and mode "a", and which would you pick to keep a running log without losing old entries?
  2. Why is a with open(...) block safer than calling open() and close() yourself? What does it guarantee even when an error occurs?
  3. In the worked example, load_list returns an empty list inside its except branch. Why is that a friendlier choice than letting the FileNotFoundError stop the program?

Where this lesson comes from

Built from

  • Data Structures and Algorithms
  • Further reading: Brian Heinold, A Practical Introduction to Python Programming

alphaPlan courses are built from taught programmes rather than invented for the web. Where a claim rests on an outside standard or a reported case, it is named above so you can check it rather than take our word for it.

This course was developed by alphaPlan Center from programs delivered in partnership with the American Corners network.

Found something unclear, outdated or improvable? Suggest an improvement