Chapter 5 · Real programs
Files
Step 1 of 21 · Concept
A file stays when the program ends
By the end of this lesson your programs keep their data in files, as text, CSV and JSON.
Your names and objects live in memory. A file lives in storage, so it stays. Writing copies text from memory to a file; reading copies it back.
Step 2 of 21 · Concept
with open(..., "w") writes a file
with open("notes.txt", "w") as f:
f.write("Buy milk\n")
f.write("Call Zoë\n")
open(path, "w")opens a file to write, and gives a file object, here namedf.f.write(text)adds the text. It adds no line ending, so each line ends with"\n".- When the indented block ends,
withcloses the file, and the text is safe in storage. - A name with no folder, such as
notes.txt, means a file in the block's own folder.
Step 3 of 21 · Demo
Write, then read
Run it. Without a mode, open reads, and f.read() gives the whole file as one text.
The text already ends in "\n", so end="" (lesson 7) adds no second one. Files are UTF-8 here: ë comes back as
written.
Step 4 of 21 · Concept
The Terminal sees the file
The program has ended, and its file is still there. See for yourself, as in lesson 8:
- Choose Open in the terminal in the ⋯ menu of Write, then read. The prompt says
~/20-files/write-read. - Type
ls. It listsmain.pyandnotes.txt. - Type
cat notes.txt. It shows the file's two lines.
The block's folder is the program's working folder: a file name with no folder means a file there.
Step 5 of 21 · Concept
Read it all, or line by line
f.read() gives the whole file as one text. for line in f: gives it one line per round.
Each line keeps its "\n". print(line) adds one more, so a blank line follows each. line.strip() removes it
(lesson 9).
Step 6 of 21 · Quiz
Predict the output
The file scores.txt holds two lines, 90 and 75. Type exactly what this program prints, blank lines included.
with open("scores.txt") as f:
for line in f:
print(line)
with open("scores.txt") as f:
for line in f:
print(line.strip())
Step 7 of 21 · Demo
See it run
Run it and compare with your answer. scores.txt is in the file tree.
Step 8 of 21 · Exercise
Your turn: total from a file
scores.txt holds one score per line. Read it line by line, and count the scores and add them up. The program must
print:
Count: 5
Total: 399
Each line is text, such as "90\n". Its number is int of the stripped text (lesson 4).
Open the file in a with, and loop over its lines. Inside the loop, update both names: one counts, one adds.
total += int(line.strip())
Step 9 of 21 · Concept
w replaces, a appends, r reads
The second argument of open is the mode:
| mode | does |
|---|---|
"w" | writes a new file, or empties the one there |
"a" | appends: writes after what the file holds |
"r" | reads; the mode open uses when you give none |
print("three", file=f) writes a line into the file, with its "\n", as print does on the screen.
Step 10 of 21 · Quiz
Predict the file
Type exactly what this program prints.
with open("notes.txt", "w") as f:
f.write("one\n")
with open("notes.txt", "w") as f:
f.write("two\n")
with open("notes.txt", "a") as f:
print("three", file=f)
with open("notes.txt") as f:
print(f.read(), end="")
Step 11 of 21 · Demo
See it run
Run it twice: the same output, since "w" starts the file fresh. Now delete the two "w" blocks, its first four
lines. Run it twice more: three piles up.
Step 12 of 21 · Demo
A missing file: FileNotFoundError
Reading a file that does not exist raises a FileNotFoundError. Catch it as any exception (lesson 17).
Run it. str(e) is the message a traceback would end with.
Step 13 of 21 · Exercise
Your turn: a log
The program starts log.txt fresh with "w". Then:
- Append the lines
load,saveandstop, with the mode"a". - Print each line of
log.txtwith its number from 1. - Read
old.txt, which does not exist: when it is missing, printno old log.
The program must print:
1 start
2 load
3 save
4 stop
no old log
Three blocks in order: one with in mode "a", one that reads, and a try around the last.
In the "a" block, print(..., file=f) three times. To number the lines, loop with enumerate(f, start=1) (lesson 11).
for n, line in enumerate(f, start=1):
Step 14 of 21 · Concept
csv reads rows of fields
A CSV file is a table as text: one row per line, the fields split by commas. The module csv reads and writes
it:
| you write | gives or does |
|---|---|
csv.reader(f) | each row as a list: ['Ada', 'math', '90'] |
csv.DictReader(f) | each row as a dictionary, keyed by the first row |
csv.writer(f) | writer.writerow(["art", "80"]) writes a row |
Give open the argument newline="" for a CSV file: csv handles the line endings itself.
Step 15 of 21 · Demo
Read a CSV
Open grades.csv in the file tree, then run it. The quotes of '90' show it is text. The program also writes
Ada's rows into a new file, ada.csv, with csv.writer.
Step 16 of 21 · Concept
json saves lists and dictionaries
JSON is a text form of lists, dictionaries, numbers, text, True, False and None. The module json turns
your data into it and back:
| you write | does |
|---|---|
json.dump(data, f) | writes data into the file f |
json.load(f) | reads it back as lists and dictionaries |
json.dumps(data) | gives the JSON text, without a file |
In JSON, None is null, and True is true. Keys keep their order; sort_keys=True sorts them.
json.dumps(data, indent=2) puts each item on its own line, indented by 2 spaces.
Step 17 of 21 · Quiz
Predict the JSON
Type exactly what this program prints.
import json
data = {"b": 1, "a": [1, 2.5, None, True]}
print(json.dumps(data))
with open("data.json", "w") as f:
json.dump(data, f, indent=2)
with open("data.json") as f:
back = json.load(f)
print(back)
print(json.dumps({"ok": True}, indent=2))
Step 18 of 21 · Demo
Save and load
Run it and compare with your answer. Then choose Open in the terminal in this block's ⋯ menu, and type
cat data.json. The file ends without a "\n", so the prompt follows its }.
Step 19 of 21 · Check yourself
Check yourself: a grade report
grades.csv holds two scores for each student. No shape this time:
- Read
grades.csvwithcsv.DictReader, and collect each name's scores. - Write
report.txt: one line per name, the name,:and the average with 1 decimal. - Read
report.txtback, and print it.
The program must print:
Ada: 85.0
Alan: 77.5
Grace: 91.5
A dictionary from each name to a list of its scores keeps them in the file's order (lesson 12).
Step 20 of 21 · Challenge · optional
Challenge: save a game
Optional.
json cannot save an Item, a dataclass. Save sword into save.json with indent=2, print the file, then load it
back into a new Item. The program must print:
{
"name": "sword",
"price": 30
}
Item(name='sword', price=30)
True
asdict(sword) gives the dictionary {'name': 'sword', 'price': 30}, which json can save.
Save with json.dump(asdict(sword), f, indent=2). It ends the file without "\n", so print it with a plain
print(f.read()). json.load then gives a dictionary d, not an Item.
loaded = Item(d["name"], d["price"])
Step 21 of 21 · Recap
Recap
- A file lives in storage, and stays when the program ends.
with open(path, mode) as f:opens a file, and closes it when the block ends."w"writes fresh,"a"appends,"r"reads; a line keeps its"\n"untilstrip().csv.DictReadergives rows of text;json.dumpandjson.loadsave and load data.- A program that appends writes its file fresh first: every Run prints the same.
Next: lesson 21 practises searching and sorting by hand, with tests.
Review
3 questions from this lesson, in a new order each time. Answer from memory: 2 of 3 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
with open("notes.txt", "w") as f:
f.write("Buy milk\n")
f.write("Call Zoë\n")
with open("notes.txt") as f:
text = f.read()
print(text, end="")
with open("scores.txt") as f:
for line in f:
print(line)
with open("scores.txt") as f:
for line in f:
print(line.strip())
count = 0
total = 0
# read scores.txt line by line: add 1 to count
# and the line's number to total
print("Count:", count)
print("Total:", total)
with open("notes.txt", "w") as f:
f.write("one\n")
with open("notes.txt", "w") as f:
f.write("two\n")
with open("notes.txt", "a") as f:
print("three", file=f)
with open("notes.txt") as f:
print(f.read(), end="")
try:
with open("missing.txt") as f:
print(f.read())
except FileNotFoundError as e:
print("Caught:", e)
with open("log.txt", "w") as f:
print("start", file=f)
# 1. append load, save and stop
# 2. print each line of log.txt, numbered from 1
# 3. read old.txt; when it is missing,
# print no old log
import csv
with open("grades.csv", newline="") as f:
rows = list(csv.DictReader(f))
print(rows[0])
total = 0
for row in rows:
total += int(row["score"])
print("Total:", total)
with open("ada.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["subject", "score"])
for row in rows:
if row["name"] == "Ada":
writer.writerow([row["subject"], row["score"]])
with open("ada.csv") as f:
print(f.read(), end="")
import json
data = {"b": 1, "a": [1, 2.5, None, True]}
print(json.dumps(data))
with open("data.json", "w") as f:
json.dump(data, f, indent=2)
with open("data.json") as f:
back = json.load(f)
print(back)
print(json.dumps({"ok": True}, indent=2))
import csv
import json
from dataclasses import dataclass, asdict
@dataclass
class Item:
name: str
price: int
sword = Item("sword", 30)
# 1. save sword into save.json, with indent=2
# 2. print the text of save.json
# 3. load it back into an Item named loaded
print(loaded)
print(loaded == sword)