Reading a Binary File
load() reads one object. A file with three records therefore needs three calls — and the interesting question is what the fourth one does, because that is what tells your loop when to stop.
1One load(), one record
student.dat holds the three records written in the last lesson. Each load() takes the next one, in the order they were written:
import pickle
f = open('student.dat', 'rb')
print(pickle.load(f))
print(pickle.load(f))
print(pickle.load(f))
print(pickle.load(f))
f.close()[1, 'Ravi', 78]
[2, 'Meera', 91]
[3, 'Amit', 65]
Traceback (most recent call last):
File "four_loads.py", line 8, in <module>
print(pickle.load(f))
^^^^^^^^^^^^^^
EOFError: Ran out of inputstudent.dat holds four records. Press load() and watch which one comes back.
—The file is open and the pointer is at the start. Nothing has been read.
read() hands back an empty string and the program carries on. A binary file has no such value to hand back, so load() raises instead. The exception is the message that the records have finished.2The loop every board answer uses
Since the end arrives as an exception, the loop is wrapped in try. Read for ever, and stop when the exception says to:
import pickle
f = open('student.dat', 'rb')
total = 0
try:
while True:
record = pickle.load(f)
print(record[0], record[1], record[2])
total = total + 1
except EOFError:
pass
f.close()
print('Records read:', total)1 Ravi 78 2 Meera 91 3 Amit 65 Records read: 3
while True:A loop with no condition. It has no way of knowing how many records there are — nothing in the file says so.
record = pickle.load(f)One record. On the call after the last one, this line raises instead of returning.
except EOFError:Which lands here. The loop is left, and the program carries on.
passThere is nothing to do about it — reaching the end of a file is not a problem. pass is the body that says 'deliberately nothing'.
f.close()Outside the try, so it runs whichever way the loop ended.
f.close() inside the try block and it is skipped the moment the exception is raised — which is every time, because that is how the loop ends. Outside, or in a finally, or use a with block.3The other way it is written — try inside, then break
You will meet a second shape in textbooks and in board answers, and it is equally correct. The try moves inside the loop, and the except ends the loop with break:
import pickle
f = open('student.dat', 'rb')
total = 0
while True:
try:
record = pickle.load(f)
print(record[0], record[1], record[2])
total = total + 1
except EOFError:
break
f.close()
print('Records read:', total)1 Ravi 78 2 Meera 91 3 Amit 65 Records read: 3
Same output, same three records, same file. What changed is where the loop is when the exception happens, and that is what decides how the loop ends.
try:
while True:
record = pickle.load(f)
...
except EOFError:
passThe exception jumps out of the whole try block, and the loop is inside it, so the loop is abandoned on the way out. Nothing more is needed — hence pass.
while True:
try:
record = pickle.load(f)
...
except EOFError:
breakThe exception is caught inside the loop, so the loop is still running and would go round again. break is what stops it.
pass belongs with try outside, break belongs with try inside. Match the body to where the try is.4Forgetting the break hangs the program
This is worth doing on purpose once. Write the second shape but leave pass in the except, and the program never ends:
import pickle
f = open('student.dat', 'rb')
while True:
try:
record = pickle.load(f)
print(record)
except EOFError:
pass # <- should have been break
f.close()[1, 'Ravi', 78] [2, 'Meera', 91] [3, 'Amit', 65] (and then nothing, for ever - the program has to be stopped by hand)
The three records print, and then it spins. The pointer is at the end of the file, so load() raises EOFError again — which is caught, and the loop goes round, and it raises again. Nothing moves the pointer and nothing ends the loop.
except with no way out is the first place to look.5Reading the records into a list
Often you want all the records in memory — to count them, sort them, or change one and write them all back. Same loop, with append() instead of print():
import pickle
f = open('student.dat', 'rb')
records = []
try:
while True:
records.append(pickle.load(f))
except EOFError:
pass
f.close()
print(records)
print('There are', len(records), 'records.')[[1, 'Ravi', 78], [2, 'Meera', 91], [3, 'Amit', 65]] There are 3 records.
6If the file is not there
'rb', like 'r', never creates a file. A program that offers to display records before any have been saved should say so politely:
import pickle
try:
f = open('student.dat', 'rb')
try:
while True:
print(pickle.load(f))
except EOFError:
pass
f.close()
except FileNotFoundError:
print('No records have been saved yet.')[1, 'Ravi', 78] [2, 'Meera', 91] [3, 'Amit', 65]
FileNotFoundError means there is no file at all. EOFError means the file exists and you have read all of it. Catching them in the same except would hide a real problem behind a normal one.7Try it
The program below writes the file and then reads it back. Add a fourth record, or print the records in a nicer format:
8Recap
Three records means three calls, in the order they were written.
The call after the last record raises it. It is the normal way a binary read finishes, not a mistake.
The standard loop. Write it the same way every time and it stops being something to think about.
The exception leaves the whole try block, taking the loop with it, so there is nothing left to do.
The except sits inside the loop, so the loop would go round again. break is what ends it. Both shapes are accepted.
try inside with pass loops for ever: load() raises at the end of the file, the except swallows it, nothing moves the pointer. Ctrl + C.
Inside, it would be skipped by the exception that ends every run.
No file at all, rather than no records left. Catch it separately.
- 1
Display every record with its position number in front of it.
Hint · A counter that starts at 1 and grows inside the loop.
- 2
Write the display program both ways —
tryoutside withpass, andtryinside withbreak— and check the output matches.Hint · It does. Either is accepted in an exam; write the one you remember.
- 3
Put
passwhere thebreakbelongs, run it, and stop it with Ctrl + C.Hint · Three records, then silence. Worth causing once so you recognise it later.
- 4
Count the records without printing any of them.
Hint · The same loop, with only the counter in the body.
- 5
Read the records into a list, then print the last one.
Hint · records[-1], once the loop has finished.
- 6
Put the
f.close()inside thetryblock and printf.closedafterwards.Hint · False — the exception jumped over it.
What happens when pickle.load() is called and no records are left?
You write the loop as while True: with try INSIDE it. What belongs in the except EOFError block?
A program prints all three records and then hangs, printing nothing and never finishing. What is the likely cause?
Why is the reading loop written as while True?
Where should f.close() go in the standard reading loop?
A program tries to read student.dat before any record has been saved. Which exception comes first?