Kodewick
Lesson 23 of 31Contents

Reading and writing files

Lesson 23 of 31 · Part 4 · Code and data · about 20 minutes

Variables vanish when a program ends. A file stays: write today's lamp count into one, and tomorrow's program can read it back. Python opens a file with open(), and then you read from it or write to it.

On this page, each example works in its own make-believe folder. It starts with the files shown on the page (like lamps.txt below), and anything an example writes stays inside that example, so you can't break anything. On your own computer, the same code works with real files in the folder you run the program from.

Writing a file

open(name, "w") opens a file for writing, creating it if it isn't there. write adds text, and close finishes the file. Then open(name) opens it again for reading, and read gives back everything in it as one string:

main.py
1f = open("notes.txt", "w")2f.write("Lamps lit: 4\n")3f.write("Oil left: 2\n")4f.close()5 6f = open("notes.txt")7print(f.read(), end="")8f.close()
Output
Lamps lit: 4
Oil left: 2

write does not start a new line by itself, so each line ends with \n. The file already ends with a new line, so end="" keeps print from adding another.

Forgetting close is an easy mistake, and if an error happens before it, the file may never be closed. A with block closes it for you when the block ends, even when an exception stops it. This is the way to open files from now on:

main.py
1with open("notes.txt", "w") as f:2    f.write("Lamps lit: 4\n")3    f.writelines(["Oil left: 2\n", "Bulbs: 7\n"])4print(f.closed)5 6with open("notes.txt") as f:7    print(f.read(), end="")
Output
True
Lamps lit: 4
Oil left: 2
Bulbs: 7

writelines writes every string in a list, one after another; like write, it adds no \n of its own.

Reading a file

Here is a file that is already in the folder. Every example on this page starts with it:

lamps.txt
Elm Street
Oak Lane
Mill Road
Harbor Way

read() gives the whole file as one string, and .splitlines() cuts it into lines. readline() reads just the next line, \n included, and readlines() gives the lines that are left as a list:

main.py
1with open("lamps.txt") as f:2    text = f.read()3print(text.splitlines())4 5with open("lamps.txt") as f:6    print(repr(f.readline()))7    print(f.readlines())
Output
['Elm Street', 'Oak Lane', 'Mill Road', 'Harbor Way']
'Elm Street\n'
['Oak Lane\n', 'Mill Road\n', 'Harbor Way\n']

repr() shows a string with its quotes and any \n, which helps when you want to see exactly what was read. Most often you will loop over the file itself, which gives one line at a time and works for files of any size:

main.py
1with open("lamps.txt") as f:2    for line in f:3        print("Light", line.strip())
Output
Light Elm Street
Light Oak Lane
Light Mill Road
Light Harbor Way
Guess first

What is different without .strip()?

1with open("lamps.txt") as f:2    for line in f:3        print(line)

Modes and appending

The second argument to open is the mode:

ModeMeaningIf the file existsIf it doesn't
"r"read (the default)reads from the startFileNotFoundError
"w"writeempties it firstcreates it
"a"appendadds to the endcreates it

Be careful with "w": opening a file that way wipes what was in it, before you write a single character. To add to a file, use "a":

main.py
1with open("lamps.txt", "a") as f:2    f.write("Quarry Road\n")3with open("lamps.txt") as f:4    print(len(f.readlines()), "streets")5 6with open("lamps.txt", "w") as f:7    f.write("Mine Gate\n")8with open("lamps.txt") as f:9    print(f.read(), end="")
Output
5 streets
Mine Gate

Tip: On your own computer, add encoding="utf-8" when a file has accents, emoji or non-English letters: open("lamps.txt", "a", encoding="utf-8"). Otherwise some systems, Windows in particular, read and write it with a different encoding and the text comes out garbled.

When the file isn't there

Reading a file that does not exist raises FileNotFoundError. Lesson 21's try handles it: here a missing save file just means starting fresh.

main.py
1try:2    with open("save.txt") as f:3        level = f.read()4except FileNotFoundError:5    level = "1"6print("Starting at level", level)7 8open("lamp.txt")
Output
Starting at level 1
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    open("lamp.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'lamp.txt'

The last line has no try around it, so the misspelled name stops the program. The message names the file it looked for, which is the first thing to check: the spelling, and whether the file is in the folder the program runs from.

Numbers in files

A file holds text, so write only takes strings. f.write(12) is a TypeError. Turn numbers into text with str() on the way in, and back with int() or float() on the way out:

main.py
1readings = [12, 7, 30]2with open("meter.txt", "w") as f:3    for r in readings:4        f.write(str(r) + "\n")5 6total = 07with open("meter.txt") as f:8    for line in f:9        total += int(line)10print("Total:", total)
Output
Total: 49

int() ignores the spaces and \n around a number, so int("12\n") is 12. An empty line would still be a ValueError.

CSV files

A CSV file (comma-separated values) is a table as plain text: one row per line, commas between the columns, and often the column names on the first line. Spreadsheets can open and save it. This one is in the folder too:

stock.csv
name,count
ore,12
lamp oil,5
wick,40
glass,3

The csv module splits the lines for you. csv.reader gives each row as a list; csv.DictReader uses the first line as names and gives each row as a dictionary:

main.py
1import csv2 3with open("stock.csv", newline="") as f:4    for row in csv.reader(f):5        print(row)6 7with open("stock.csv", newline="") as f:8    for row in csv.DictReader(f):9        if int(row["count"]) < 10:10            print("Order more", row["name"])
Output
['name', 'count']
['ore', '12']
['lamp oil', '5']
['wick', '40']
['glass', '3']
Order more lamp oil
Order more glass

Every value comes back as a string, so int() is needed before comparing. Open CSV files with newline=""; the csv module handles line endings itself. Writing is the mirror image: csv.writer writes lists, csv.DictWriter writes dictionaries.

main.py
1import csv2 3orders = [{"name": "lamp oil", "count": 20}, {"name": "glass", "count": 10}]4with open("orders.csv", "w", newline="") as f:5    writer = csv.DictWriter(f, fieldnames=["name", "count"])6    writer.writeheader()7    writer.writerows(orders)8 9with open("orders.csv") as f:10    print(f.read(), end="")
Output
name,count
lamp oil,20
glass,10

JSON

JSON is a text format for nested data: dictionaries, lists, strings, numbers, True/False and None (written true, false and null in JSON). It is how many programs and websites exchange data. json.dump writes a value to a file and json.load reads it back, types and all (mostly: tuples come back as lists); dumps and loads (with an s, for string) do the same with a string.

main.py
1import json2 3town = {"name": "Pixel Town", "lamps": 4, "streets": ["Elm", "Oak"], "lit": True}4with open("town.json", "w") as f:5    json.dump(town, f)6 7with open("town.json") as f:8    loaded = json.load(f)9print(loaded["streets"])10print(loaded == town)11 12text = json.dumps(town)13print(text)
Output
['Elm', 'Oak']
True
{"name": "Pixel Town", "lamps": 4, "streets": ["Elm", "Oak"], "lit": true}

Unlike a CSV file, the numbers come back as numbers and the list as a list. json.dumps(town, indent=2) spreads the text over several lines, which is easier to read.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it. Each one starts with lamps.txt and stock.csv in the folder.

Exercise 1

Count the streets

Read lamps.txt and print how many streets it lists.

It should print
4 streets
exercise1.py
1count = 02# read lamps.txt here3print(count, "streets")
Output
Press Run or Ctrl+Enter
Exercise 2

One more street

Append Quarry Road as a new line to lamps.txt, then read the file and print every street with its number.

It should print
1 Elm Street
2 Oak Lane
3 Mill Road
4 Harbor Way
5 Quarry Road
exercise2.py
1with open("lamps.txt") as f:2    for line in f:3        print(line.strip())
Output
Press Run or Ctrl+Enter
Exercise 3

Total stock

Use csv.DictReader on stock.csv to add up the count column.

It should print
Total: 60
exercise3.py
1import csv2 3total = 04print("Total:", total)
Output
Press Run or Ctrl+Enter

What you learned

  • open(name, mode) opens a file; with open(...) as f: closes it for you, even after an error.
  • read() gives everything, readline() one line, readlines() a list; for line in f: goes line by line, with \n on each line.
  • "w" empties the file and writes, "a" adds to the end, "r" reads; a missing file raises FileNotFoundError.
  • Files hold text: str() numbers on the way in, int() or float() on the way out.
  • csv reads and writes tables (reader, writer, DictReader, DictWriter); json saves nested data (dump, load, dumps, loads).