CH 4 · FILE HANDLING
Unit 1 · Computational Thinking and Programming

Chapter 4
File Handling

Every program you have written so far forgets everything the moment it ends. This chapter makes data survive.

1.1 Where we have got to

Chapters 1–3

Data structures, functions, and a way to survive errors. All of it lives in RAM — and RAM is emptied when the program exits.

The problem, concretely

You write a marks program. It reads five students, computes the average, prints a report. You run it again tomorrow — and you have to type all five students in again.

What a file gives you

A named area of secondary storage — a disk — that outlives the program. Write once, read on every future run, share it with another program entirely.

Definition

File — a named collection of related data stored on a secondary storage device, which persists after the program that created it has ended.

Why this is the chapter that uses everything

Reading a file gives you strings (Ch 1). Processing it needs loops and lists (Ch 1). You will wrap it in functions (Ch 2). And a missing file raises FileNotFoundError, so it all sits inside try/except/finally (Ch 3).

1.2 Three kinds of file, one dataset

Text file — .txt

Characters, human-readable, line by line. Openable in Notepad.

marks.txtRiya 88 CS
Aarav 91 CS
Kabir 76 IP

Binary file — .dat

Raw bytes written by pickle. Not human-readable, but Python objects come back exactly as they went in.

marks.dat — real bytes, as hex8004 9558 0000 0000 0000 005d
9428 5d94 288c 0452 6979 6194
4b58 8c02 4353 9465 5d94 288c
0541 6172 6176 944b 5b68 0365

CSV file — .csv

Text, but with one record per line and fields separated by commas. Opens in Excel.

marks.csvName,Marks,Stream
Riya,88,CS
Aarav,91,CS
The spine of this chapter

The same five students, stored three ways. Riya, Aarav, Kabir, Meera and Dev appear in every single slide from here on. Once you stop re-reading the data, you start seeing the difference between the three formats — which is the whole point.

A fourth thing you already use

input() and print() are file operations too — on the standard input and standard output streams. Everything below is the same idea, pointed at a disk.

1.3 How this chapter runs

Five stops

  1. Opening and closing — modes, and why closing matters
  2. Reading textread, readline, readlines, and the loop
  3. Writing textwrite, writelines, "w" vs "a"
  4. Binary filespickle, and search / append / update
  5. CSV filescsv.reader, csv.writer, newline=""

The four operations, on all three kinds

OperationTextBinaryCSV
Create / write
Read
Search
Append
Updaterewriterewrite
The exam pattern

Questions are set as “write a function that …” — count, search, append, update. Learn the four shapes and every question is a variation.

2.1 The first complete program

first.pyf = open("marks.txt", "r")
data = f.read()
f.close()

print(data)
print("chars =", len(data))
The file has five lines. What prints? 🤔
OutputRiya 88 CS Aarav 91 CS Kabir 76 IP Meera 94 CS Dev 59 IP chars = 57
Where the blank line comes from

The file ends with a newline after Dev 59 IP. read() keeps it, and print adds its own — so you get one blank line. That blank line is a mark.

The three steps, always

open — connect the program to the file and get a file object.
read or write, using that object's methods.
close — break the connection and flush anything pending.

2.2 The file modes

ModeMeansIf the file does not existIf it does exist
"r"read — the defaultFileNotFoundErroropens at the start
"w"writecreates iterases everything in it
"a"appendcreates itopens at the end, keeps contents
"r+"read and writeFileNotFoundErroropens at the start
"rb" "wb" "ab"the same three, in binaryas aboveas above
open(name) with no mode means open(name, "r").
The most destructive line in the syllabus

f = open("marks.txt", "w") — on an existing file this empties it instantly, before you have written a single byte. If you meant to add to the file, you wanted "a". There is no undo.

How to remember which is which

read · write-from-scratch · append-to-the-end. The b suffix only changes how the bytes are interpreted, never where the file pointer starts.

2.3 "w" versus "a", demonstrated

modes.pyf = open("log.txt", "w")
f.write("first\n")
f.close()

f = open("log.txt", "w")
f.write("second\n")
f.close()

f = open("log.txt", "a")
f.write("third\n")
f.close()

with open("log.txt") as f:
    print(f.read())
Three writes. How many lines survive? 🤔
Outputsecond third
first is gone

The second open(..., "w") wiped the file before writing. Only the "a" on the third open preserved what was already there.

The trailing blank line again

"third\n" ends in a newline, and print adds another — so the output really does end with an empty line. Write it in the exam.

2.4 Why close() matters — and with

the form the paper expectsf = open("marks.txt", "r")
for line in f:
    print(line.strip())
f.close()
Three things an unclosed file costs you

Written data may sit in a buffer and never reach the disk.
The operating system limit on open files is finite.
On some systems another program cannot open it.

with — closes automaticallywith open("marks.txt", "r") as f:
    for line in f:
        print(line.strip())

print("file closed?", f.closed)
OutputRiya 88 CS Aarav 91 CS Kabir 76 IP Meera 94 CS Dev 59 IP file closed? True
Definition

with — opens the file and guarantees it is closed when the block ends, even if an exception is raised inside. No close() is needed.

Board policy — learn both

with is better Python and NCERT teaches it. But most marking schemes are written with openclose, and some questions say "close the file" explicitly. Write with where you are free to, and never forget close() when you are not.

2.5 When the file is not there

WRONG — main.py1f = open("absent.txt", "r")
2print(f.read())
Chapter 3 — name the exception. 🤔
TracebackTraceback (most recent call last): File "main.py", line 1, in <module> f = open("absent.txt", "r") ^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'absent.txt'
GUARDEDtry:
    f = open("absent.txt", "r")
    print(f.read())
except FileNotFoundError:
    print("absent.txt does not exist")
finally:
    print("done")
Outputabsent.txt does not exist done
Back-link

Chapter 3 §3.2 promised this exact traceback. "r" on a missing file is the single most common real exception in the syllabus — and the reason except FileNotFoundError is worth a mark on its own.

NCERT calls it IOError

FileNotFoundError is a subclass of OSError, which is the same class the older name IOError refers to. Both are accepted; FileNotFoundError is what Python actually prints, so prefer it.

2.6 Closing safely when things go wrong

safeclose.pyf = None
try:
    f = open("marks.txt", "r")
    print(f.readline().strip())
    print(int("abc"))
except ValueError:
    print("bad number in the file")
finally:
    if f is not None:
        f.close()
    print("closed:", f.closed)
The amber line always fails. Does the file get closed? 🤔
OutputRiya 88 CS bad number in the file closed: True
This is Chapter 3 §6.2, made real

finally ran even though the try block was abandoned halfway. That is exactly what it is for — and file closing is the textbook example the syllabus uses.

Or just use with

with open(...) does all of this for you, including the if f is not None. Understand the long form, then prefer the short one.

3.1 read() — the whole file as one string

wholefile.pyf = open("marks.txt", "r")
data = f.read()
f.close()

print(type(data))
print(len(data))
print(data[:10])
One string, or five? 🤔
Output<class 'str'> 57 Riya 88 CS
Definition

read() — returns the entire remaining contents of the file as a single string, newlines included.
read(n) — returns at most n characters.

read(n) — the pointer movesf = open("marks.txt", "r")
print(f.read(4))
print(f.read(3))
f.close()
OutputRiya 88

The second call did not start again from the beginning. It continued from character 4 — and picked up the space before 88.

3.2 The file pointer

R i y a 8 8 C S \n 0 1 2 3 4 5 6 7 8 9 10 at open after read(4) after read(3) more tell() → where it is seek(0) → send it back
Every read advances the pointer. That is why a second read() returns an empty string.
pointer.pyf = open("marks.txt", "r")
print(f.tell())
print(f.read(4))
print(f.tell())
f.seek(0)
print(f.tell())
print(f.readline().strip())
f.close()
Output0 Riya 4 0 Riya 88 CS
Definition

tell() — returns the current position of the file pointer, in bytes from the start.
seek(n) — moves the file pointer to byte n.

3.3 readline() and readlines()

readline — one linef = open("marks.txt", "r")
print(f.readline())
print(f.readline().strip())
f.close()
OutputRiya 88 CS Aarav 91 CS
The blank line, explained once and for all

readline() keeps the \n at the end of the line, and print adds another. The second call used .strip() — and the blank line disappeared.

readlines — a list of linesf = open("marks.txt", "r")
L = f.readlines()
f.close()

print(L)
print(len(L), L[2].strip())
Output['Riya 88 CS\n', 'Aarav 91 CS\n', 'Kabir 76 IP\n', 'Meera 94 CS\n', 'Dev 59 IP\n'] 5 Kabir 76 IP
Copy that list exactly

Quotes round every item, and a visible \n inside each one. Both are marked.

MethodReturnsTypeUse when
read()the whole fileone stryou want the text as a block
read(n)n charactersone stryou want a fixed chunk
readline()the next lineone strone line at a time
readlines()every linelist of stryou need to index or count lines
Note the s: readline gives a string, readlines gives a list.

3.4 The loop you will write most often

walk.pyf = open("marks.txt", "r")
for line in f:
    print(line.strip())
f.close()
OutputRiya 88 CS Aarav 91 CS Kabir 76 IP Meera 94 CS Dev 59 IP
A file object is iterable

for line in f hands you one line at a time, in order, and stops at the end of the file — with no counter, no readlines(), and without loading the whole file into memory.

Three equivalent ways

for line in f: — best, and memory-friendly.
for line in f.readlines(): — same result, whole file in RAM.
while True: line = f.readline()if line == "": break — the long form some question papers show.

Always .strip()

Every line except possibly the last carries a trailing \n. Forgetting to strip it is why if line == "CS" mysteriously fails and why your output is double-spaced.

3.5 Counting — the standard 3-mark question

count.pylines = 0
words = 0
chars = 0

with open("marks.txt", "r") as f:
    for line in f:
        lines = lines + 1
        words = words + len(line.split())
        chars = chars + len(line)

print("Lines:", lines)
print("Words:", words)
print("Chars:", chars)
Five lines of three words each. 🤔
OutputLines: 5 Words: 15 Chars: 57
Why chars is 57 and not 52

len(line) counts the \n too — five of them. If the question says "excluding newlines", use len(line.strip()) and the answer becomes 52. Read which one is asked.

split() with no argument

It splits on any run of whitespace and discards empties — so it handles double spaces and the trailing newline correctly. That is why it is the right tool for counting words.

3.6 Searching — the other standard question

search.pycount = 0

with open("marks.txt", "r") as f:
    for line in f:
        if "CS" in line:
            count = count + 1
            print(line.strip())

print("CS students:", count)
Which of the five? 🤔
OutputRiya 88 CS Aarav 91 CS Meera 94 CS CS students: 3
The shape of every search question

a counter at 0   loop over the lines   an if that tests the line   act and count   report after the loop.
Only step ③ changes between questions.

splitting into fieldstotal = 0
n = 0

with open("marks.txt", "r") as f:
    for line in f:
        parts = line.split()
        total = total + int(parts[1])
        n = n + 1

print(parts)
print("Average =", round(total / n, 2))
Output['Dev', '59', 'IP'] Average = 81.6
Two things to notice

parts holds the last line's fields after the loop — a local name outliving the loop, exactly as in Chapter 1.
And parts[1] is the string '59', so int() is compulsory. Add strings and you get '8891...'.

4.1 write() and writelines()

writeone.pyf = open("out.txt", "w")
f.write("Hello\n")
f.write("Class XII\n")
f.close()

f = open("out.txt", "r")
print(f.read())
f.close()
OutputHello Class XII
write adds no newline

Unlike print, write() writes exactly what you give it. Leave out the \n and the whole file becomes one line: HelloClass XII.

writemany.pylines = ["one\n", "two\n", "three\n"]

f = open("nums.txt", "w")
f.writelines(lines)
f.close()

with open("nums.txt") as f:
    print(f.read())
Outputone two three
Definition

write(s) — writes the string s to the file.
writelines(L) — writes every string in the list L. It adds no separators either — the \ns must already be in the strings.

Only strings

f.write(88) raises TypeError: write() argument must be str, not int. Convert first: f.write(str(88)). This is the writing-side twin of needing int() when you read.

5.1 The problem with text

Everything comes back as a string

You wrote 88, an int. You read back '88', a str. You wrote a list of five records; you read back one long string you have to split() apart again by hand.

What we actually want

Put a Python object — a dictionary, a list of lists — into a file, and get the same object back, types intact, with no parsing.

Definition

Binary file — a file that stores data as raw bytes rather than as readable characters. It cannot be opened meaningfully in a text editor.

Pickling — converting a Python object into a stream of bytes for storage. Unpickling is the reverse.

Also called

Serialisation and deserialisation. Both words appear in question papers; they mean pickling and unpickling.

The trade

You gain exact types and one-line storage of any structure. You lose readability — nobody, including you, can inspect a .dat file without a Python program.

5.2 dump() and load()

onerecord.pyimport pickle

student = {"name": "Riya",
           "marks": 88,
           "stream": "CS"}

f = open("one.dat", "wb")
pickle.dump(student, f)
f.close()

f = open("one.dat", "rb")
back = pickle.load(f)
f.close()

print(back)
print(type(back), back["marks"])
What type comes back? 🤔
Output{'name': 'Riya', 'marks': 88, 'stream': 'CS'} <class 'dict'> 88
Definition

pickle.dump(obj, f) — writes obj to the open binary file f. Note the order: object first, file second.
pickle.load(f) — reads one object back from f and returns it.

A dictionary went in; a dictionary came out

back["marks"] is the int 88 — not '88'. No split(), no int(). That is the entire selling point of pickle.

The mode is "wb" / "rb", always

import pickle at the top, and never a plain "w" or "r".

Why binary and not text mode for pickle?

Because pickle produces bytes, not characters. Text mode wraps the file in an encoder that assumes every byte is part of a valid UTF-8 character — and pickle's very first byte, 0x80, is not.

f = open("marks.dat", "r")
data = pickle.load(f)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte

Text mode also silently translates newline bytes on Windows, which would corrupt any pickled data containing the byte 0x0d. Both problems vanish in binary mode, because binary mode does no interpretation at all — it moves bytes exactly as they are.

5.3 Storing all five records at once

writebin.pyimport pickle

records = [["Riya", 88, "CS"],
           ["Aarav", 91, "CS"],
           ["Kabir", 76, "IP"],
           ["Meera", 94, "CS"],
           ["Dev", 59, "IP"]]

with open("marks.dat", "wb") as f:
    pickle.dump(records, f)

with open("marks.dat", "rb") as f:
    data = pickle.load(f)

print(data)
print(len(data))
One dump call for five records. 🤔
Output[['Riya', 88, 'CS'], ['Aarav', 91, 'CS'], ['Kabir', 76, 'IP'], ['Meera', 94, 'CS'], ['Dev', 59, 'IP']] 5
One object, not five

The whole list was pickled as a single object, so a single load() brings it all back. This is by far the easiest pattern to get right, and it is what the marking scheme shows.

The alternative you may see

You can call dump() once per record and then load() repeatedly in a while True loop until EOFError is raised. It works, and it is the reason EOFError is in the Chapter 3 table — but the single-list version is simpler and safer.

5.4 Searching a binary file

searchbin.pyimport pickle

with open("marks.dat", "rb") as f:
    data = pickle.load(f)

for rec in data:
    if rec[1] > 85:
        print(rec[0], rec[1])
Who scored above 85? 🤔
OutputRiya 88 Aarav 91 Meera 94
Compare with the text version

In §3.6 you had to split() the line and int() the field before comparing. Here rec[1] is already an int, so > 85 just works.

The shape of a binary search

open "rb"   load() the whole structure   loop over it in memory   test each record.
There is no line-by-line reading — you get the objects back and search them like any list.

5.5 Updating a binary file

updatebin.pyimport pickle

with open("marks.dat", "rb") as f:
    data = pickle.load(f)

for rec in data:
    if rec[0] == "Kabir":
        rec[1] = rec[1] + 5

with open("marks.dat", "wb") as f:
    pickle.dump(data, f)

with open("marks.dat", "rb") as f:
    print(pickle.load(f))
Kabir had 76. 🤔
Output[['Riya', 88, 'CS'], ['Aarav', 91, 'CS'], ['Kabir', 81, 'IP'], ['Meera', 94, 'CS'], ['Dev', 59, 'IP']]
Update = read, change, write back

load() everything into a list.
change it in memory — this is Chapter 1 mutability doing the work.
reopen in "wb" and dump() the whole thing back.

Why rec[1] = … is enough

rec is a label on a list inside data — the same object, not a copy. Mutating it changes data. That is Chapter 1 §6.6 and Chapter 2 §7.2, and it is why no re-assignment into data is needed.

5.6 Check yourself

Two programs. marks.dat is a valid pickled file; empty.dat is a zero-byte file. Name the exception each raises.

Question 1a
1import pickle
2f = open("marks.dat", "r")
3data = pickle.load(f)
TracebackTraceback (most recent call last): File "main.py", line 3, in <module> data = pickle.load(f) ^^^^^^^^^^^^^^ UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte

The mode is wrong. Text mode tried to decode pickle's raw bytes as UTF-8 characters and choked on the very first one.

Question 1b
1import pickle
2f = open("empty.dat", "rb")
3data = pickle.load(f)
TracebackTraceback (most recent call last): File "main.py", line 3, in <module> data = pickle.load(f) ^^^^^^^^^^^^^^ EOFError: Ran out of input

The mode is right this time — there is simply nothing in the file to unpickle.

Two different failures, same line

Both crash on pickle.load(f), and the traceback looks almost identical. The exception name is the only thing that tells you whether to fix the open() mode or to check that the file was written at all.

6.1 The format that both worlds can read

Definition

CSV — Comma Separated Values. A text file in which each line is one record and the values within a record are separated by commas.

Why it exists

A text file is readable but unstructured. A binary file is structured but unreadable. CSV is both — a human can open it in Notepad, and Excel, Google Sheets and every database in the world can import it.

marks.csvName,Marks,Stream
Riya,88,CS
Aarav,91,CS
Kabir,76,IP
Meera,94,CS
Dev,59,IP
The first line

Name,Marks,Stream is the header row — field names, not data. It is optional, but almost every file has one, and almost every exam question requires you to skip it.

You could do this with split(",")

And for a file this simple it would work. The csv module exists because real data contains commas inside fields — "Sharma, Riya",88,CS — and quotes, and embedded newlines. csv.reader handles all of it; split(",") silently gets it wrong.

6.2 Writing a CSV

writecsv.pyimport csv

rows = [["Name", "Marks", "Stream"],
        ["Riya", 88, "CS"],
        ["Aarav", 91, "CS"],
        ["Kabir", 76, "IP"],
        ["Meera", 94, "CS"],
        ["Dev", 59, "IP"]]

f = open("marks.csv", "w", newline="")
w = csv.writer(f)
w.writerows(rows)
f.close()

print(open("marks.csv").read())
Note the ints in the list. 🤔
OutputName,Marks,Stream Riya,88,CS Aarav,91,CS Kabir,76,IP Meera,94,CS Dev,59,IP
Definition

csv.writer(f) — creates a writer object bound to the open file f.
writerow(list) — writes one record.
writerows(list of lists) — writes many.

The ints became text

88 was written as the characters 8 and 8. A CSV is a text file — it has no idea what a number is. That is why reading it back gives you strings.

6.3 Reading a CSV

readcsv.pyimport csv

with open("marks.csv", "r", newline="") as f:
    r = csv.reader(f)
    for row in r:
        print(row)
What type is each row? 🤔
Output['Name', 'Marks', 'Stream'] ['Riya', '88', 'CS'] ['Aarav', '91', 'CS'] ['Kabir', '76', 'IP'] ['Meera', '94', 'CS'] ['Dev', '59', 'IP']
Every field is a string

'88', with quotes — not 88. Any arithmetic needs int(row[1]) first. This is the single most common CSV mistake, and it is the same lesson as input() in Chapter 1 §2.8.

Definition

csv.reader(f) — returns an object you iterate over. Each iteration yields one record as a list of strings.

6.4 Skipping the header, and filtering

filtercsv.pyimport csv

with open("marks.csv", "r", newline="") as f:
    r = csv.reader(f)
    header = next(r)
    print("header:", header)
    for row in r:
        if int(row[1]) > 85:
            print(row[0], row[1])
Four lines. 🤔
Outputheader: ['Name', 'Marks', 'Stream'] Riya 88 Aarav 91 Meera 94
Two things doing the work

next(r) pulls one record off the reader and moves on, so the for loop starts at the first real record.
int(row[1]) is compulsory — without it you would be comparing the string '88' with the int 85, which raises TypeError.

The alternative

for row in r: then if row[0] == "Name": continue also works and is what some question papers show. next() is cleaner and reads better.

6.5 Appending a record — and newline=""

appendcsv.pyimport csv

f = open("marks.csv", "a", newline="")
w = csv.writer(f)
w.writerow(["Ishan", 67, "IP"])
f.close()

print(open("marks.csv").read())
OutputName,Marks,Stream Riya,88,CS Aarav,91,CS Kabir,76,IP Meera,94,CS Dev,59,IP Ishan,67,IP
Why newline=""

The csv module writes its own record terminator. In text mode Python also translates \n to the platform's line ending. newline="" switches that translation off so the two do not collide.

Write it every time

On every open() that a csv.reader or csv.writer will touch — read and write. It is a one-mark habit and it costs nothing.

What actually happens if I leave newline="" out?

It depends on the operating system, which is exactly why the rule is stated as absolute.

On Windows — text mode turns each \n into \r\n. The csv writer has already ended the record with \r\n, so the file gets \r\r\n and every second line comes out blank. This is the famous symptom.
On Linux and macOS — no translation happens, so omitting it makes no visible difference at all.

So a student on Linux may never see the bug, ship the habit, and lose the mark — or produce a broken file for a teacher on Windows. The syllabus requires newline="", and it is correct on every platform, which is the whole reason to write it unconditionally rather than "when it breaks".

Reading matters too, for a different reason: without it, a field containing an embedded newline is split across two records.

6.6 The three formats, compared

 Text .txtBinary .datCSV .csv
Human-readableYesNoYes
Open mode"r" "w" "a""rb" "wb""r" + newline=""
Module needednonepicklecsv
What you read backstrthe original objectlist of str
Types preservedNoYesNo
Read methodsread readline readlinespickle.loadcsv.reader
Write methodswrite writelinespickle.dumpwriterow writerows
Other software can read itYesNoYes — Excel
Same five students, three files. If you can fill this table from memory, the chapter is done.

7.1 Everything on one screen

Opening and closing

open(name, mode) "w" erases the file "a" appends "r" is the default with … as f closes for you f.close() in finally FileNotFoundError

Text files

read() → one string readline() → one line readlines() → a list for line in f .strip() removes the \n write() adds no newline writelines() adds no separator seek() / tell()

Binary files

import pickle "wb" / "rb", never "w" / "r" pickle.dump(obj, f) pickle.load(f) types survive update = load, change, dump back EOFError on an empty file

CSV files

import csv newline="" on every open csv.reader(f) csv.writer(f) writerow / writerows every field is a string next(r) skips the header

7.2 Exam question 1 — find the output

3 marks

marks.txt contains the five lines shown earlier. Write the output.

with open("marks.txt", "r") as f:
    lines = f.readlines()

print(lines[0].strip())
print(lines[-1].strip())
print(len(lines))
print(lines[1][:5])
Are you ready for the answer? 🤔
Answer
OutputRiya 88 CS Dev 59 IP 5 Aarav

readlines() gives a list, so all of Chapter 1's list operations apply — [0], [-1], len(), and slicing the string inside. lines[1][:5] is the first five characters of 'Aarav 91 CS\n'.

7.3 Exam question 2 — write the function

3 marks

Write a function count_vowel_lines() that reads marks.txt and displays those lines that begin with a vowel, then returns how many there were.

Are you ready for the answer? 🤔
Answer
vowellines.pydef count_vowel_lines():
    n = 0
    with open("marks.txt", "r") as f:
        for line in f:
            if line[0] in "AEIOUaeiou":
                n = n + 1
                print(line.strip())
    return n

print("Count:", count_vowel_lines())
OutputAarav 91 CS Count: 1
Where the three marks are

opening in "r" and closing (or using with).
testing line[0] against both cases — a question that says "a vowel" means either case unless it says otherwise.
return n, not print(n) — the question said "returns".

Careful

line[0] on an empty line raises IndexError. If a file may contain blank lines, test if line.strip() and line[0] in ….

7.4 Exam question 3 — binary file

3 marks

marks.dat holds a pickled list of [name, marks, stream] records. Write a function topper() that returns the record with the highest marks.

Are you ready for the answer? 🤔
Answer
topper.pyimport pickle

def topper():
    with open("marks.dat", "rb") as f:
        data = pickle.load(f)

    best = data[0]
    for rec in data:
        if rec[1] > best[1]:
            best = rec
    return best

print(topper())
Output['Meera', 94, 'CS']
Where the three marks are

import pickle and mode "rb".
pickle.load(f) into a variable.
the max-finding loop, seeded with data[0] rather than 0 — a record, not a number.

Note what you did not have to do

No split(), no int(). rec[1] came back as an int because pickle preserved the type.

7.5 Exam question 4 — CSV file

3 marks

marks.csv has a header row and then the five records. Write a function stream_count() that returns a dictionary giving how many students are in each stream.

Are you ready for the answer? 🤔
Answer
streams.pyimport csv

def stream_count():
    counts = {}
    with open("marks.csv", "r", newline="") as f:
        r = csv.reader(f)
        next(r)
        for row in r:
            s = row[2]
            counts[s] = counts.get(s, 0) + 1
    return counts

print(stream_count())
Output{'CS': 3, 'IP': 2}
Where the three marks are

newline="" on the open.
next(r) to discard the header — without it you would count a phantom 'Stream' student.
the counts.get(s, 0) + 1 idiom from Chapter 1 §8.5.

This is the Chapter 1 frequency count

Exactly the banana program, pointed at a file instead of a string. That is what this whole chapter is — old algorithms, new source of data.

7.6 Exam question 5 — two-mark theory

2 + 2 marks
  1. Differentiate between a text file and a binary file. Give one advantage of each.
  2. What is the difference between readline() and readlines()? What does each return?
Are you ready for the answer? 🤔
Answer 1

A text file stores data as readable characters, organised into lines terminated by a newline. It is opened in modes such as "r" and "w", and everything read from it is a string.

A binary file stores data as raw bytes with no character interpretation. It is opened in "rb" / "wb" and, using pickle, returns the original Python objects with their types intact.

Advantage of text: it can be read and edited by a human in any editor, and shared with other software. Advantage of binary: data types are preserved, so no parsing or conversion is needed, and it is faster and more compact.

Answer 2

readline() reads one line from the current position and returns it as a string, including its trailing newline. At end of file it returns an empty string.

readlines() reads all the remaining lines and returns them as a list of strings, each still carrying its newline.

7.7 One last challenge

Challenge

marks.txt holds the five records. What is printed, and what is in the file afterwards?

f = open("marks.txt", "r")
print(f.readline().strip())
print(len(f.readlines()))
print(f.read())
print("[" + f.read() + "]")
f.close()
Four lines. The last two are the interesting ones. 🤔
Answer
OutputRiya 88 CS 4 []

Line 1readline() took the first record and left the pointer at the start of line 2.

Line 2readlines() took the remaining four, so the length is 4, not 5. The pointer is now at the end of the file.

Lines 3 and 4 — every further read() returns the empty string, because there is nothing left. print("") emits a blank line, and the brackets on the last line prove the string really is empty.

The file is unchanged. It was opened "r"; reading never modifies. To restart you would need f.seek(0).

End of Chapter 4

Next: Stacks

You can now store and retrieve data. Chapter 5 is about the order in which you take it back out — the last-in-first-out discipline that runs undo buttons, browser back, and Python's own function calls.

Carry these three forward

A stack is built on a listappend and pop, which you have used all chapter.
The call stack in a traceback is a real stack; Chapter 5 explains why it is drawn innermost-last.
The classic stack question reads records from a file and pushes them — so §5 and §6 of this chapter are the input to the next one.