Chapter 2: File Handling in Python

At the end of the 1960s, a small group of researchers at Bell Laboratories in the United States began building an operating system for a spare minicomputer. Ken Thompson and Dennis Ritchie, who led that work, had to answer a question that sounds simple and is not: what exactly is a file?

The systems of that time usually answered it with structure. A file was a collection of records, each record of a fixed size, laid out in a form the operating system itself understood. If your data did not fit the shape the expected, you fought the system.

Unix, the system they built, answered differently. A file would be nothing but a sequence of bytes, one after another, with no structure imposed from outside. Whatever meaning those bytes carried — a letter, a table of marks, a photograph, a program — was the business of the program that read them, not of the operating system.

That decision has outlived the machine it was made on. When you open a file in Python today, you are opening a stream of bytes and deciding, by the way you open it, how those bytes should be interpreted. Everything in this chapter follows from that one idea.

Fig. 2.0

Fig. 2.0 – File byte interpretation.

NoteCuriosity Corner

Q1. If every file on a disk is only a sequence of bytes, what makes one file a text file and another a binary file? Q2. Why does opening an existing file in one mode destroy everything already inside it, while another mode leaves the old contents perfectly safe? Q3. After a program has read part of a file and reads again, how does it know where to continue from? Q4. If a program has already written its data, why must the file still be closed before the program ends? Q5. A list or a dictionary exists only while the program runs. How can one be stored in a file and brought back later exactly as it was?

Keep these five questions in mind as you read. By the end of the chapter you will be able to answer every one of them.

2.1 Introduction to Files

Every program you have written so far has followed the same shape. It takes some input, works on it, and displays a result on the screen. The moment the program ends, all of it is gone — the input, the intermediate values and the result alike.

For small exercises that is no loss. For anything real it is a serious limitation. A school office that records the marks of six hundred students cannot type them in afresh every morning. A shop that maintains a stock list needs yesterday’s list to still be there today. What such work needs is a way for data to outlive the program that produced it.

This chapter is about that. You will learn how a Python program can write data out to permanent storage, read it back later, move about inside it, and store whole Python objects such as lists and dictionaries so that they come back exactly as they were.

2.1.1 Why a Program Needs Permanent Storage

Consider a fragment as ordinary as this one.

marks = int(input("Enter the marks: "))
print("The marks entered are", marks)

While the program runs, the value typed by the user is held in the variable marks. That variable occupies space in the computer’s random access memory (RAM), also called primary memory. When the program finishes, the interpreter releases that space, and the value disappears with it. A variable’s lifetime lasts only as long as the program is under execution.

RAM behaves this way by design, and it also behaves this way physically. It is volatile memory — its contents survive only while the machine is powered. Switch the computer off, and whatever was in RAM is lost, whether a program was running or not.

Where data can survive

Data that must survive is therefore kept somewhere else: on a hard disk, a solid-state drive, a memory card or a pen drive. These are secondary storage devices. They are slower to reach than RAM and much larger, and — the property that matters here — they retain their contents when the power goes off.

Writing data to secondary storage is what gives it persistence: the data exists independently of any one run of any one program. A marks list written to a disk on Monday can be read back on Friday, by the same program, by a different program, or by a person opening it in a text editor.

Table 2.1 – Primary memory and secondary storage | Feature | Primary memory (RAM) | Secondary storage | |—|—|—| | Purpose | Holds the program and its data while it executes | Holds data and programs for the long term | | Contents on power off | Lost | Retained | | Access speed | Very fast; the processor works directly with it | Much slower than RAM | | Typical capacity | Smaller | Much larger | | How a Python program uses it | Through variables and objects | Through files |

The last row of Table 2.1 is the whole point of this chapter. A program reaches RAM through variables. It reaches secondary storage through files.

Persistent storage Statement: Data written to secondary storage remains available after the program that wrote it has ended and after the machine has been switched off. In words: Storage keeps data; memory only borrows it. Why it holds: Secondary storage devices record data physically — magnetically on a disk, as trapped charge in a solid-state drive — in a form that does not depend on continuous power, unlike the volatile cells of RAM. Used for: Saving results, reusing data across runs, sharing data between programs, and building records that grow over months and years. Watch out: Persistence is not automatic. Data reaches storage only when the program deliberately writes it there, and, as Section 2.3 will show, only when the write is properly completed.

Q. A program prints its result on the screen. Is that result stored?

A. No. The screen is an output device, not a storage device. The characters are displayed and nothing is recorded; scroll the window away or close it, and there is no copy anywhere to go back to. Storing means writing the data to a secondary storage device.

2.1.2 What a File Is

You already use files without calling them that. A photograph on a phone, a Python program saved as marks.py, a song, a spreadsheet of bus timings — each of these is a named collection of data kept on a storage device, and each can be found again by its name.

That is exactly what a file is. It is a file because it has a name, because it lives on secondary storage, and because it holds data that stays there until something deliberately changes it.

NoteDefinition

A file is a named location on a secondary storage medium where data are stored permanently, so that they can be accessed again later.

The name matters more than it first appears. Storage devices hold data as vast numbers of bytes with no natural boundaries between one collection and the next. It is the operating system that groups those bytes, gives the group a name, and records where it begins and how far it runs. Without that naming, a program would have no way to ask for one particular set of data rather than another.

Names, extensions and folders

A file name is usually written in two parts separated by a dot — practice.txt, stock.dat, library.py. The part after the dot is the file name extension, and it announces what kind of data the file is expected to hold: .txt for plain text, .py for a Python program, .csv for comma separated values, .dat for a data file.

An extension is a convention, not a rule enforced by the machine. Renaming a photograph to photo.txt does not turn it into text; it only misleads the software that trusts the name. The bytes inside decide what a file really is, which is the subject of the next section.

Files are organised into folders, and a folder may contain further folders. So the full description of a file is its name together with the path of folders leading to it. Section 2.3 returns to this when a program has to open a file that is not sitting in the same folder as the program itself.

Fig. 2.1

Fig. 2.1 – RAM versus secondary storage.

The word “file” was borrowed from office work long before computers were common: a file was a set of related papers kept together and labelled so it could be found again. The borrowing is exact. The label is the file name, the cabinet is the storage device, and the folder is still called a folder.

Data files and program files

Both library.py and stock.dat are files, but a program treats them very differently. A .py file holds instructions and is handed to the Python interpreter to be executed. A data file holds the information a program works on — marks, names, quantities, prices — and is opened, read and written by that program.

Everything from Section 2.3 onwards concerns data files. The program is one thing; the data it keeps is another, and separating the two is what allows the same program to be run tomorrow on a file that has grown by two hundred records.

Open the folder in which you save your Python programs. List five files in it, and for each one write down its name, its extension and what you expect the file to contain. Then say which of the five are program files and which are data files.

2.2 Types of Files

Whatever a file holds — a paragraph of English, a photograph, a song, a program — the storage device records the same thing: a long run of bytes, each byte a group of eight bits, each bit a 0 or a 1. The device knows nothing about letters or pixels. It knows bytes.

So if the storage is identical, where does the difference between one kind of file and another come from? It comes from the agreement about what those bytes are supposed to mean, and from the software that honours that agreement.

On that basis, data files are divided into two kinds: text files and binary files. The division runs through the whole of this chapter, because Python opens the two differently, reads them differently and writes them differently.

2.2.1 Text Files

A text file stores characters — the letters of an alphabet, digits, punctuation marks, spaces and a few control characters. Each character is stored as the numeric code assigned to it by an encoding scheme, and that number is what actually sits on the disk.

The oldest and simplest such scheme is the American Standard Code for Information Interchange (ASCII), which gives every English letter, digit and common punctuation mark a number from 0 to 127. A more general scheme is Unicode, which extends the same idea to the scripts of the world, including Devanagari, Tamil and Bengali, and which Python 3 uses by default.

Table 2.2 shows a few characters with their ASCII codes. Read the third column as the eight-bit pattern that is physically stored.

Table 2.2 – Some characters, their ASCII codes and the byte stored for each | Character | ASCII code | Byte stored | |—|—|—| | A | 65 | 01000001 | | a | 97 | 01100001 | | 0 | 48 | 00110000 | | space | 32 | 00100000 | | , | 44 | 00101100 | | newline | 10 | 00001010 |

Notice the fourth and the sixth rows. A space and a newline are characters like any other, with codes of their own, and they occupy a byte each exactly as A does. A file of ten visible letters spread over two lines is not ten bytes long.

Nothing in Table 2.2 is visible when you open the file. A text editor such as Notepad reads the byte 01000001, looks it up as 65, finds that 65 stands for A, and paints the letter A on the screen. That translation, done character by character, is the whole service a text editor provides.

NoteDefinition

A text file is a file in which the data are stored as a sequence of characters, each character represented by its code in an encoding scheme such as ASCII or Unicode, so that the contents can be read directly by any text editor.

Files with extensions such as .txt, .py, .csv and .html are text files. That includes every Python program you have ever written: a .py file is plain text, which is why you can open it in any editor and read it.

Q. If a .py file is just a text file, what makes it a program?

A. Nothing about the file itself — only what is done with it. The same bytes are meaningless characters to a photo viewer, readable text to an editor, and executable instructions to the Python interpreter. Meaning lives in the software that reads the file, never in the disk.

Lines and the end-of-line character

A text file is one continuous run of characters; the disk has no notion of a “line”. Lines exist because a special character is placed where one line is to end and the next to begin. This is the end of line (EOL) character, and in Python the default is the newline, written \n in a string.

Although it is written with two symbols, \n is a single character occupying a single byte, as Table 2.2 records. When an editor or a program meets it, everything after it is shown starting on a fresh line. Some operating systems store a line ending as a two-character combination instead; when a file is opened in text mode, Python takes care of the difference, so a program written on one machine reads correctly on another.

Within a line, values are separated by whatever the writer of the file chose — most often a space, but a comma or a tab (\t) just as commonly, since both are easy for a program to search for when the line has to be split back into separate values.

Fig. 2.2 shows the whole idea in one picture: a short two-line file, the characters it appears to hold, and the bytes actually stored.

Fig. 2.2

Fig. 2.2 – Text file character and byte representation.

Open a text editor, type one short sentence, and save the file with a .txt extension. Now type the same sentence in a word processor and save it as a document file. Compare the sizes of the two files on disk. Write down which is larger, and suggest what the extra bytes in the larger file could be recording.

2.2.2 Binary Files

A binary file is also a run of bytes, but its bytes are not codes for characters. They are the data themselves, in whatever form the application that created the file decided upon — the colour values of the dots in a photograph, the sound levels in a recording, the machine instructions of an executable program, the compressed contents of an archive.

NoteDefinition

A binary file is a file in which data are stored as a stream of bytes that represent the data directly rather than as codes for readable characters, and which therefore requires a specific program that knows the format in order to be interpreted.

Open such a file in a text editor and you get nonsense. The editor does what it always does — treats each byte as a character code and displays the corresponding character — but the bytes were never character codes, so the result is a jumble of symbols. Nothing is wrong with the file; the wrong software is reading it.

Fig. 2.3

Fig. 2.3 – Binary file opened in different formats.

This dependence on format has a consequence worth noticing. In a text file, a damaged byte spoils one character and the rest of the file still reads. In a binary file, a single altered bit can shift the meaning of everything that follows, and the file may become unusable to the application that created it. Nor can you inspect the damage by eye, because the contents were never readable in the first place.

Python can read and write both kinds of file. What changes is how the file is opened, and — for binary files holding Python objects — the module used to write to it, which Section 2.8 introduces.

opening a binary file in text mode What students write:

f = open("photo.jpg", "r")
data = f.read()

What happens: the file exists and opens without complaint, but the read usually fails with a UnicodeDecodeError, because Python is trying to decode picture data as characters. Depending on the encoding in use, it may instead succeed and hand back a meaningless string. The fix: open a binary file in a binary mode, open("photo.jpg", "rb"), so that Python returns the raw bytes and does not attempt to decode them as text. The mode has to match what the file actually holds.

2.2.3 Choosing Between a Text File and a Binary File

The two kinds of file are not rivals; each answers a different need. A text file is chosen when the data are readable information that people or other programs may want to inspect, edit or exchange — a marks list, a log of bus timings, a set of records exported for another application. Its great advantage is that nothing special is needed to look at it.

A binary file is chosen when the data are not characters at all, or when a Python object such as a list or a dictionary is to be stored and later recovered in exactly its original form. Storing that object as text would mean converting it to characters on the way out and rebuilding it on the way back in; a binary file stores its structure directly.

Table 2.3 – Text file and binary file | Feature | Text file | Binary file | |—|—|—| | What a byte stands for | The code of a character in ASCII or Unicode | Part of the data itself, in the application’s own format | | Readable by a text editor | Yes | No; the contents appear as meaningless symbols | | Line structure | Present; lines are separated by the EOL character \n | Not present; there are no lines, only bytes | | Typical extensions | .txt, .py, .csv, .html | .dat, .jpg, .mp3, .exe | | Effect of a small corruption | Usually spoils one character | May make the whole file unusable | | Storing a Python list or dictionary | Must be converted to characters and rebuilt on reading | Can be stored and recovered with its structure intact |

Q1. If every file on a disk is only a sequence of bytes, what makes one file a text file and another a binary file?

A. Not the disk, and not the bytes in themselves — both kinds of file are stored the same way. The difference is the agreement about what the bytes mean. In a text file every byte, or small group of bytes, is the code of a character in a scheme such as ASCII or Unicode, so any text editor can translate it back into readable characters. In a binary file the bytes carry the data directly in a format that only the program that understands that format can interpret. The file name extension merely announces which agreement is being followed; it is the software reading the file that has to honour it.

2.3 Opening and Closing a Text File

A program cannot reach into a file the way it reaches into a variable. Between the program in memory and the file on the disk there has to be a connection, and that connection must be set up before any data can move and taken down when the work is done.

Python makes the setting-up explicit. Every piece of file handling in this chapter has the same three-part shape: open the file, work on it, close it. Miss the first step and the program has nothing to work with; miss the last, and the data you thought you had written may never reach the disk.

Fig. 2.4

Fig. 2.4 – The file object as a link to the file.

2.3.1 Opening a File Using open()

A file is opened with the built-in function open(). It is given the name of the file and the way the file is to be used, and it hands back an object that stands for the open file.

That object is the file object, also called the file handle. It is not the file and it is not the file’s contents. It is the program’s link to the file: every later operation — writing, reading, moving about — is requested through it.

NoteDefinition

A file object is the object returned by open(), through which a program performs all operations on an open file, and which keeps track of the file being used, the mode it was opened in, and the current position within it.

opening a file

file_object = open(file_name, access_mode)

Parts: file_name is a string giving the name of the file, with a path if the file is not in the program’s own folder; access_mode is a string saying what the file may be used for, and may be left out, in which case Python uses "r". Returns / Effect: returns a file object linked to the file; depending on the mode, the file may also be created or emptied at this moment. Example:

fobj = open("marks.txt", "r")
Where Python looks for the file

Written as a plain name, "marks.txt" refers to a file in the current working directory — normally the folder the program itself is running from. A file kept elsewhere must be named by its absolute path, the full route to it starting from the drive or the root of the file system.

NoteDefinition

An absolute path is the complete location of a file, written from the root of the file system or from the drive letter down to the file name, so that it identifies the file without depending on where the program is running from.

On a Windows machine a path is written with backslashes, and a backslash inside a Python string begins an escape sequence. So the path is written either with the backslash doubled or as a raw string.

fobj = open("D:\\school\\records\\marks.txt", "r")
fobj = open(r"D:\school\records\marks.txt", "r")

Both statements name the same file. On systems that use forward slashes, no such precaution is needed.

2.3.2 File Access Modes

The second argument to open() is the file access mode, also called the processing mode. It answers three questions at once: may this program read the file, may it write to the file, and what is to happen to whatever the file already contains.

That third question is the one that catches people out. Two modes will happily create the file if it is missing, one of them will silently empty it if it is present, and one refuses to open a file that does not already exist.

The mode is built from a few letters. r asks for reading, w for writing, a for appending at the end. A + added to any of these asks for the other operation as well, so that the same file object can both read and write. A b added to any of them says that the file is to be treated as a binary file rather than as text; without it, Python works in text mode and transfers strings.

Table 2.4 sets out the six modes with their binary forms. The last column gives the initial file pointer position — where in the file the next operation will act — a matter Section 2.6 takes up in detail.

Table 2.4 – File access modes, their effect on an existing file, and the initial file pointer position | Text mode | Binary mode | Opens the file for | If the file does not exist | If the file already exists | Initial position | |—|—|—|—|—|—| | "r" | "rb" | Reading only | An error is raised | Contents are left untouched | Beginning of the file | | "r+" | "rb+" | Reading and writing | An error is raised | Contents are left untouched | Beginning of the file | | "w" | "wb" | Writing only | A new empty file is created | All existing contents are erased | Beginning of the file | | "w+" | "wb+" | Writing and reading | A new empty file is created | All existing contents are erased | Beginning of the file | | "a" | "ab" | Appending only | A new empty file is created | Contents are kept; new data go to the end | End of the file | | "a+" | "ab+" | Appending and reading | A new empty file is created | Contents are kept; new data go to the end | End of the file |

Read the fourth column carefully. Only "w", "w+", "a" and "a+" create a missing file. Asking for "r" or "r+" on a file that is not there raises a FileNotFoundError, and the program stops unless the error is handled. This is reasonable: reading a file that does not exist has no sensible meaning, whereas writing to one does.

Now read the fifth column. The difference between "w" and "a" on an existing file is total. Opening in "w" mode empties the file at the moment of opening — before a single character has been written, the old contents are gone and cannot be recovered. Opening in "a" mode keeps everything and positions the pointer past the last character, so that new data are added after the old.

Fig. 2.5

Fig. 2.5 – Behaviour of read, write and append modes.

Q. A program opens a file in "w" mode, then crashes before writing anything. What is in the file?

A. Nothing. The emptying happens when the file is opened, not when data are written. The old contents were destroyed the moment open() was called, and the crash leaves an empty file behind.

using "w" when data are to be added What students write:

fobj = open("attendance.txt", "w")
fobj.write("Present: 41\n")
fobj.close()

What happens: the program runs without any error message and the file afterwards holds one line. Every earlier day’s record has been wiped out, because "w" empties the file at opening. Nothing warns the user, which is what makes this mistake expensive. The fix: use append mode when existing data must survive — open("attendance.txt", "a") — and reserve "w" for the case where the file is genuinely being written from scratch.

Q2. Why does opening an existing file in one mode destroy everything already inside it, while another mode leaves the old contents perfectly safe?

A. Because the modes describe two different intentions. "w" means “this file is to hold what I am about to write” — so Python truncates it to zero length as soon as it is opened, giving the program a clean file to fill. "a" means “this file already holds something and I want to add to it” — so Python keeps the contents and sets the file pointer past the last character, and every write goes to the end. The destruction in "w" is not an accident or a side effect of writing; it happens at the moment of opening, which is why a program that opens in "w" and then does nothing still leaves an empty file.

2.3.3 Attributes of a File Object

Besides carrying out reading and writing, a file object holds a little information about the file it is linked to. Three attributes are worth knowing, and they are read simply by naming them — no brackets, because they are attributes and not methods.

Table 2.5 – Attributes of a file object | Attribute | What it gives | |—|—| | name | The name of the file, as it was passed to open() | | mode | The access mode in which the file was opened | | closed | True if the file has been closed, False if it is still open |

The short program below opens a file, reports these three attributes, closes the file and asks again. It is worth running, because it makes the difference between an open and a closed file visible.

reporting the attributes of a file object Problem: Create a text file, display the name and mode of the file object, and show the value of its closed attribute both before and after the file is closed. Assumes: No file need exist beforehand; the mode "w" creates notes.txt in the current working directory.

fobj = open("notes.txt", "w")
print("File name :", fobj.name)
print("Mode      :", fobj.mode)
print("Closed now:", fobj.closed)
fobj.close()
print("Closed now:", fobj.closed)

Sample output:

File name : notes.txt
Mode      : w
Closed now: False
Closed now: True

Identifier description:

Identifier Type Purpose
fobj File object Link to the file notes.txt, opened in write mode

Notes: The file object continues to exist after close() — the name fobj is still valid, and fobj.closed can still be read. What no longer exists is the connection to the file, so any attempt to read or write through fobj now raises a ValueError.

2.3.4 Closing a File Using close()

When a program has finished with a file, it should close it. The method that does so takes no arguments and is called on the file object.

closing a file

file_object.close()

Parts: file_object is the object that open() returned for this file. Returns / Effect: breaks the link between the program and the file, writes out any data still waiting in memory, and releases the resources the operating system had set aside for the open file. Example:

fobj.close()

The phrase “data still waiting in memory” is the important one. When a program writes to a text file, the characters do not travel to the disk one at a time — that would be extremely slow. They are collected in a region of memory called a buffer, and are transferred to the disk when the buffer fills up or when the file is closed. Closing the file therefore does not merely tidy up; it is what guarantees that the last of the data actually arrives.

A program that ends without closing its files usually gets away with it, because Python closes them as it shuts down. But “usually” is not a basis for handling someone’s records. If the program is stopped, or crashes, or the machine loses power while data sit in the buffer, those data are simply lost, and the file on disk is missing its last lines with nothing to indicate that anything is wrong.

There is a second reason. An operating system allows only a limited number of files to be open at one time. A program that opens files in a loop and never closes them will eventually be refused when it asks for one more.

Q4. If a program has already written its data, why must the file still be closed before the program ends?

A. Because “written” and “on the disk” are not the same thing. A write() call places the characters in a buffer in memory, and the buffer is emptied to the disk only when it fills or when the file is closed. Until then the data exist only in RAM, which loses everything if the program is interrupted or the power fails. Closing the file flushes the buffer, so the last lines actually reach the disk, and it also releases the resources the operating system reserved for the open file so that other files can be opened later.

Write a program that opens a text file in "w" mode, writes one line to it, and then ends without calling close(). Open the file in a text editor and note what you find. Now add the close() call, run the program again, and compare. Write one sentence explaining the difference.

2.3.5 Opening a File Using the with Clause

Closing a file is easy to promise and easy to forget, especially when a function has several possible exit points, or when an error occurs part way through the work and the close() call is never reached. Python offers a construct that removes the problem: the with clause.

opening a file with the with clause

with open(file_name, access_mode) as file_object:
    statements that use the file

Parts: file_name and access_mode are exactly as in open(); file_object is the name by which the file object is known inside the block. Returns / Effect: the file is opened, the indented block is executed, and the file is closed automatically when control leaves the block — whether it leaves normally or because an error was raised. Example:

with open("marks.txt", "r") as fobj:
    content = fobj.read()
print(content)

Notice where the print() sits in that example. It is outside the block, and it works: the file is closed by then, but content is an ordinary string variable and survives perfectly well. What would fail is calling fobj.read() outside the block, because the file object is no longer connected to anything.

Table 2.6 – Closing with close() and closing with the with clause | Feature | Explicit close() | The with clause | |—|—|—| | Who closes the file | The programmer, by writing the call | Python, on leaving the block | | If an error occurs during the work | The close() call is skipped and the file stays open with data possibly unflushed | The file is still closed correctly | | If the programmer forgets | The file is left open until the program ends | Cannot arise; there is nothing to forget | | Extent of the open file | From open() until the close() call, wherever that is | Exactly the indented block, visible at a glance |

The with clause is the safer habit, and this book uses it wherever a file is opened for a single, self-contained piece of work. Where a program opens a file, works on it across several stages and closes it deliberately, the explicit form is clearer, and Section 2.7 uses it that way.

2.4 Writing to a Text File

To put data into a file, the file must first be opened in a mode that permits writing — "w", "w+", "a", "a+" or "r+". Which of these is chosen decides what happens to the data already in the file and where the new data land, exactly as Table 2.4 sets out.

Once the file is open, the writing itself is done by one of two methods. write() puts a single string into the file; writelines() puts a whole sequence of strings into it. Both send characters to the file at the current position of the file pointer, and both leave the pointer just after the last character written.

One thing neither method does is add a line break for you. A text file has lines only because newline characters were placed in it, and placing them is the program’s responsibility.

2.4.1 The write() Method

The write() method takes one string and writes it to the file. It also reports back: its return value is the number of characters written on that call, which for a text file is simply the length of the string passed to it.

writing a string to a text file

file_object.write(string)

Parts: file_object is the object returned by open() for a file opened in a writing mode; string is the text to be written. Returns / Effect: returns the number of characters written; the characters are placed at the current position of the file pointer, which then moves past them. Example:

fobj.write("Welcome to the school library\n")

Worked in the interactive shell, the effect is easy to see.

>>> fobj = open("greet.txt", "w")
>>> fobj.write("Welcome to the school library\n")
30
>>> fobj.close()

The shell displays 30 because that is what write() returned, and the string does contain thirty characters — twenty-nine visible ones and the newline. Although \n is typed as a backslash and a letter, it is a single character and is counted as one, as Section 2.2 established.

Numbers must be converted first

A text file stores characters, so write() insists on being given a string. Handing it an integer or a float is an error, not a convenience Python quietly performs for you. The value has to be converted with str() first.

>>> fobj = open("count.txt", "w")
>>> total = 245
>>> fobj.write(str(total))
3
>>> fobj.close()

The return value is 3, because str(245) is the three-character string "245". What is stored in the file is the codes for the characters 2, 4 and 5 — not the number 245 in any arithmetic sense. Reading it back later gives a string, and turning it into a number again needs int().

writing a number without converting it What students write:

fobj = open("count.txt", "w")
fobj.write(245)

What happens: the program stops with TypeError: write() argument must be str, not int. Nothing is written to the file. The fix: convert the value first — fobj.write(str(245)). Anything that goes into a text file must be a string, because a text file has no way of holding anything but characters.

Q. Two write() calls are made one after the other. Will the file show two lines?

A. No. The second string begins exactly where the first one ended, on the same line, with nothing between them. A new line appears only if a \n was included at the end of the first string. write() writes precisely what it is given and adds nothing.

Writing a set of records

Most real writing is not one string but many, produced by a loop. The method is short enough to state in full before any code is written.

writing a set of records to a text file Input: A collection of records held in memory, and the name of the file to be written. Output: A text file holding one record per line. Method:

1. Open the file in write mode, which creates it if it is absent and empties it if it is present.
2. Repeat steps 3 to 4 for each record in the collection.
3. Build one string from the fields of the record, converting any number to a string.
4. Add a newline character at the end of that string and write it to the file.
5. Close the file so that the buffer is flushed to disk.

Why it works: Each record becomes one string ending in a newline, so the characters arrive in the file in order and the newlines divide them into lines. Reading the file back line by line therefore returns the records one at a time. Watch out: If the collection is empty, the loop never runs, and step 1 has already emptied the file — the result is a file of zero length. If the earlier contents mattered, append mode should have been used.

writing a stock list to a text file Problem: A shop’s stock is held as a list of item names with their quantities. Write each item on its own line of a text file as the name followed by the quantity, and display the number of characters written for each line. Assumes: No file need exist beforehand; stock.txt is created in the current working directory.

items = [("Notebook", 12), ("Pen", 30), ("Eraser", 25)]

with open("stock.txt", "w") as fobj:
    for name, qty in items:
        line = name + " " + str(qty) + "\n"
        chars = fobj.write(line)
        print("Characters written:", chars)

print("stock.txt has been created")

Sample output:

Characters written: 12
Characters written: 7
Characters written: 10
stock.txt has been created

Identifier description:

Identifier Type Purpose
items List of tuples The stock, each tuple holding an item name and its quantity
fobj File object Link to stock.txt, opened in write mode
name String Item name taken from the current tuple
qty Integer Quantity taken from the current tuple
line String The complete line to be written, ending in \n
chars Integer Number of characters returned by write()

Notes: The quantity is converted with str() because a text file can hold only characters. The with clause is used so that the file is closed even if an error interrupts the loop. The counts printed differ from line to line simply because the lines differ in length.

The file left behind holds three lines:

Notebook 12
Pen 30
Eraser 25

A trace makes the growth of the file visible, character by character.

the loop of the stock-list program, on the list [("Notebook", 12), ("Pen", 30), ("Eraser", 25)]

Iteration name qty line chars File contents after the write
1 Notebook 12 Notebook 12\n 12 Notebook 12 and a line break
2 Pen 30 Pen 30\n 7 the above, then Pen 30 and a line break
3 Eraser 25 Eraser 25\n 10 the above, then Eraser 25 and a line break

Result: The printed counts are 12, 7 and 10, and the file ends with 29 characters in three lines. The trace shows that the file grows only where the program writes, and that each line exists as a line only because the program put a \n at its end.

2.4.2 The writelines() Method

When several strings are already sitting in a list or a tuple, writing them one at a time in a loop is unnecessary. The writelines() method takes the whole sequence and writes every string in it, in order.

writing a sequence of strings to a text file

file_object.writelines(sequence_of_strings)

Parts: sequence_of_strings is any iterable object — a list or a tuple, most often — every element of which must be a string. Returns / Effect: returns nothing; the strings are written one after another at the current file pointer position. Example:

fobj.writelines(["Route 12 - 7:10 am\n", "Route 5 - 8:25 am\n"])

Used in the shell on a small bus timetable, it looks like this.

>>> fobj = open("timings.txt", "w")
>>> routes = ["Route 12 - 7:10 am\n", "Route 5 - 8:25 am\n", "Route 9 - 9:40 am\n"]
>>> fobj.writelines(routes)
>>> fobj.close()

Nothing is displayed after the call to writelines(), and that is not an oversight. Unlike write(), this method returns no count at all, so the shell has nothing to show.

The name is misleading in one respect. writelines() does not write lines; it writes strings. Each element of the list above ends in \n because the programmer put it there, and it is those newlines, not the method, that produce three lines in the file. Remove them and all three strings run together into one long line, as Fig. 2.6 shows.

Fig. 2.6

Fig. 2.6 – Writing sequence elements with and without newlines.

Every element must also be a string. A list of integers passed to writelines() raises a TypeError on the first element it reaches, for the same reason that write() refuses an integer — a text file holds characters and nothing else. Numbers must be converted before the list is handed over.

Table 2.8 – write() and writelines() | Feature | write() | writelines() | |—|—|—| | What is passed to it | A single string | An iterable of strings, such as a list or a tuple | | Return value | The number of characters written | None | | Newlines | Not added; must be inside the string | Not added; must be inside each element | | Typical use | Writing one line, or building lines in a loop | Writing a collection of strings already prepared | | If given a number | Raises TypeError | Raises TypeError on reaching the number |

Neither method is more powerful than the other; the choice is one of convenience. If the strings must be built as the program goes along, write() inside a loop is natural. If they already exist as a list, writelines() says the same thing in one line.

Take the three-element list of bus timings above, remove the \n from every element, and run the code again. Open timings.txt in a text editor and describe what you see. Then rewrite the same task using write() inside a for loop so that the three timings appear on three separate lines.

2.5 Reading from a Text File

Writing puts data on the disk; reading brings them back. For reading, the file must be opened in a mode that permits it — "r", "r+", "w+" or "a+" — and, apart from "a+", the file pointer begins at the very start of the file.

Python offers three reading methods, and the difference between them is not what they read but what shape they hand back. read() returns one string, readline() returns one line as a string, and readlines() returns a list of strings. Choosing the right one is mostly a matter of asking what shape the rest of the program needs.

All three examples in this section work on the same file, library.txt, which holds three book titles, one to a line. Its last line does not end with a newline.

Wings of Fire
Discovery of India
The Guide

2.5.1 The read() Method

The read() method returns the contents of the file as a single string. Given a number, it returns at most that many characters; given nothing, it returns everything from the current position to the end of the file.

reading characters from a text file

file_object.read(n)

Parts: n is the number of characters to be read, counted from the current file pointer position; it may be omitted, and a negative value has the same effect as omitting it. Returns / Effect: returns the characters read as one string, and leaves the file pointer just after the last character returned. Example:

first_five = fobj.read(5)

Worked in the shell, two successive calls show how the pointer carries over from one to the next.

>>> fobj = open("library.txt", "r")
>>> fobj.read(5)
'Wings'
>>> fobj.read(3)
' of'
>>> fobj.close()

The second call did not start again at the beginning. It resumed exactly where the first one stopped, which is why it returned a leading space — the sixth character of the file is the space after Wings.

Called with no argument, read() returns the whole file as one long string, newlines and all.

>>> fobj = open("library.txt", "r")
>>> print(fobj.read())
Wings of Fire
Discovery of India
The Guide
>>> fobj.close()

What print() displays as three lines is a single string of 42 characters. The line breaks you see on the screen are the two \n characters sitting inside that string; they are part of the data, not a formatting choice made by print().

reading the same file twice without repositioning What students write:

fobj = open("library.txt", "r")
whole = fobj.read()
first_line = fobj.readline()
print(first_line)

What happens: no error is raised, but first_line is the empty string and the print() shows a blank line. The first call consumed the entire file, so the pointer is already at the end and there is nothing left to read. The fix: read the file once and work on what you have, or move the pointer back to the start with fobj.seek(0) before reading again. Section 2.6 explains seek().

2.5.2 The readline() Method

The readline() method reads one line — that is, it reads characters until it meets a newline and returns them together with that newline. It is the natural method when a file is to be dealt with one record at a time.

reading one line from a text file

file_object.readline(n)

Parts: n, if given, is the largest number of characters to be read; reading stops earlier if a newline is met first. Omitting n, or giving a negative value, reads the whole line. Returns / Effect: returns the line read, as a string, with the newline character at its end if there was one; the file pointer moves to the start of the next line. Example:

line = fobj.readline()

Four calls on library.txt show the whole behaviour, including what happens at the end.

>>> fobj = open("library.txt", "r")
>>> fobj.readline()
'Wings of Fire\n'
>>> fobj.readline()
'Discovery of India\n'
>>> fobj.readline()
'The Guide'
>>> fobj.readline()
''
>>> fobj.close()

Three points are worth drawing out of that sequence. The first two lines come back with their \n attached, because the newline is genuinely part of what was stored. The third comes back without one, because the file simply ends there. And the fourth call returns the empty string.

That empty string is how Python reports the end of file (EOF) — the point past which there is nothing more to read. It is not the same as a blank line in the file: a blank line is stored as a lone newline character and comes back as '\n', a string of length one, while EOF returns a string of length zero.

NoteDefinition

End of file is the position immediately after the last character of a file. A reading method called when the file pointer has reached it returns an empty string, which is how a program detects that the file has been read completely.

Given a number, readline() reads at most that many characters, but never past the end of the line.

>>> fobj = open("library.txt", "r")
>>> fobj.readline(5)
'Wings'
>>> fobj.readline(30)
' of Fire\n'
>>> fobj.close()

The second call asked for thirty characters and got nine. It stopped at the newline because the line ended there, and that is precisely the difference between readline(n) and read(n) — the latter would have run straight on into the second line.

Fig. 2.7 traces the pointer through these calls.

Fig. 2.7

Fig. 2.7 – Pointer movement during repeated readline() calls.

2.5.3 The readlines() Method

The readlines() method reads all the remaining lines at once and returns them as a list of strings, one string per line, each keeping the newline that ended it.

reading all lines of a text file into a list

list_of_lines = file_object.readlines()

Parts: file_object is a file open for reading. Returns / Effect: returns a list whose elements are the lines of the file, in order, each with its terminating newline where one exists; the file pointer is left at the end of the file. Example:

titles = fobj.readlines()
>>> fobj = open("library.txt", "r")
>>> fobj.readlines()
['Wings of Fire\n', 'Discovery of India\n', 'The Guide']
>>> fobj.close()

The last element has no \n, exactly as the third readline() call did not, and for the same reason. A common mistake is to compare such strings with plain titles and wonder why the comparison fails — 'The Guide' matches, but 'Wings of Fire\n' does not match 'Wings of Fire'. The string method strip() removes the newline along with any surrounding spaces, and is worth applying as a habit whenever lines read from a file are to be compared or joined.

Once the lines are in a list, the ordinary string methods do the rest of the work. split() breaks a line into its words and returns them as a list.

>>> line = 'Discovery of India\n'
>>> line.split()
['Discovery', 'of', 'India']

Called without an argument, split() divides at whitespace and discards it, so the trailing newline disappears of its own accord. Given an argument, it divides at that string instead — line.split(",") for a line of comma-separated values.

Fig. 2.8 sets the three methods side by side, because what they return is what makes them different.

Fig. 2.8

Fig. 2.8 – Data structures returned by reading methods.

Table 2.9 – read(), readline() and readlines() | Feature | read() | readline() | readlines() | |—|—|—|—| | How much it reads | The whole file, or n characters | One line, or at most n characters of it | All the remaining lines | | What it returns | A single string | A single string | A list of strings | | Newlines | Kept inside the returned string | Kept at the end of the returned string | Kept at the end of each element | | Where the pointer ends up | After the last character read | At the start of the next line | At the end of the file | | At end of file | Returns an empty string | Returns an empty string | Returns an empty list | | Suited to | Handling the file as one block of text | Processing one record at a time | Working on all lines together in a list |

The last row is the real basis for choosing. A file of a few hundred lines can be pulled into a list without a thought; a file of several lakh lines is better handled one line at a time, because readlines() would place every one of those lines in memory at once.

2.5.4 Looping Over a File Object

There is a fourth way to read, and in practice it is the most common of all. A file object can be used directly in a for loop, and it then yields the file one line at a time, exactly as readline() would, until the end of the file is reached.

with open("library.txt", "r") as fobj:
    for line in fobj:
        print(line, end="")
Wings of Fire
Discovery of India
The Guide

The end="" is not decoration. Each line already carries its own newline and print() normally adds one more, so without it the output would come out double-spaced. This is the commonest surprise in file reading, and it disappears once you remember that the newline belongs to the data.

This form has a practical advantage over readlines(): it never holds more than one line in memory at a time, so it works on a file of any size. It is also the shortest way to say “do this for every line”, which is why the programs later in this chapter use it.

The program below reads a file with readlines() and then counts what is in it.

counting the lines and words in a text file Problem: Read a text file and display the number of lines it contains and the total number of words in it. A word is any group of characters separated by whitespace. Assumes: The file library.txt exists in the current working directory and holds:

Wings of Fire
Discovery of India
The Guide
with open("library.txt", "r") as fobj:
    lines = fobj.readlines()

line_count = len(lines)
word_count = 0

for line in lines:
    word_count = word_count + len(line.split())

print("Number of lines :", line_count)
print("Number of words :", word_count)

Sample output:

Number of lines : 3
Number of words : 8

Identifier description:

Identifier Type Purpose
fobj File object Link to library.txt, opened for reading
lines List of strings All the lines of the file, each with its newline
line_count Integer Number of lines, found as the length of the list
word_count Integer Running total of words found so far
line String The line being examined in the current pass

Notes: Counting lines needs no loop at all, because readlines() has already divided the file into lines and len() counts them. The words need a loop because each line must be split separately. Using split() with no argument means the trailing newline is discarded automatically, so 'Wings of Fire\n' yields three words and not four.

the counting loop of the program above, on the three lines of library.txt

Pass line line.split() Words in this line word_count after the pass
start — — — 0
1 Wings of Fire\n ['Wings', 'of', 'Fire'] 3 3
2 Discovery of India\n ['Discovery', 'of', 'India'] 3 6
3 The Guide ['The', 'Guide'] 2 8

Result: The loop ends with word_count equal to 8, and the program prints 3 and 8. The trace shows that the newline never becomes a word, because whitespace is what split() divides at and then throws away.

Q. The file is opened in "r" mode and does not exist. What does the program do?

A. It stops at the open() call with a FileNotFoundError, before any reading is attempted. Read modes never create a file. A program that must survive a missing file should check for it, or handle the exception, rather than assume the file is there.

Practical link: This task exercises reading from a text file and processing its contents; file it under Python programs in the practical report file. Create a text file notes.txt holding at least six lines of ordinary English. Write a program that reads the file and displays only those lines that contain more than five words, each preceded by its line number. Test your program on a file whose last line is blank, and state what your program does with that line.

2.6 Setting Offsets in a File

Every method met so far reads from wherever the file pointer happens to be and leaves it further along. Reading is therefore sequential: to reach the fiftieth line you read the forty-nine before it.

That is fine for a file you intend to process from end to end, and wasteful for a file in which you want one particular piece. Python allows a program to ask where the pointer is, and to move it wherever it likes. The two methods that do this are tell() and seek().

The position is measured in bytes from the beginning of the file, and is called the offset. For a file of ordinary English text, where each character occupies one byte, an offset of 17 means the eighteenth character — counting from zero, as Python counts everything.

NoteDefinition

An offset is the position of the file pointer, measured as the number of bytes from the beginning of the file. An offset of 0 is the position just before the first byte.

Both examples in this section use the file fares.txt, which holds two bus fares, one to a line, with no newline after the second.

Route 12 fare 15
Route 05 fare 20

The first line takes 17 bytes — sixteen visible characters and the newline — so the second line begins at offset 17, as Fig. 2.9 shows.

Fig. 2.9

Fig. 2.9 – Measuring file offsets in bytes.

2.6.1 The tell() Method

The tell() method reports the current offset. It takes no argument and changes nothing; it only answers the question “where am I?”

finding the current file pointer position

file_object.tell()

Parts: file_object is any open file object. Returns / Effect: returns an integer, the current offset in bytes from the beginning of the file. The file and the pointer are left untouched. Example:

position = fobj.tell()

Interleaving tell() with reads makes the movement of the pointer visible.

>>> fobj = open("fares.txt", "r")
>>> fobj.tell()
0
>>> fobj.read(5)
'Route'
>>> fobj.tell()
5
>>> fobj.readline()
' 12 fare 15\n'
>>> fobj.tell()
17
>>> fobj.close()

Read that sequence as one story. The file opens at offset 0. Five characters are read, so the pointer stands at 5. readline() then reads from 5 to the end of the first line — the twelve characters ' 12 fare 15\n' — leaving the pointer at 17, which is exactly where the second line begins.

Q3. After a program has read part of a file and reads again, how does it know where to continue from?

A. The file object remembers. Along with the name of the file and the mode, it holds a file pointer — an offset counted in bytes from the beginning of the file — and every read or write moves that pointer past the data it handled. The next call simply starts from wherever the pointer was left. tell() reports that offset, which is how the shell session above could show it changing from 0 to 5 to 17 without anything else in the program keeping track of it.

2.6.2 The seek() Method

The seek() method moves the pointer. Once it has been moved, the next read or write acts at the new position, so a program can return to the start of a file, jump to a known record, or skip a header without reading it.

moving the file pointer

file_object.seek(offset, reference_point)

Parts: offset is the number of bytes by which the pointer is to be placed; reference_point says what the offset is counted from, and may be omitted, in which case it is 0. Returns / Effect: places the file pointer at the requested position and returns the new absolute offset. No data are read or written. Example:

fobj.seek(17)

The three permitted reference points are listed in Table 2.11.

Table 2.11 – Reference points accepted by seek() | Value | Offset is counted from | |—|—| | 0 | The beginning of the file (the default) | | 1 | The current position of the file pointer | | 2 | The end of the file |

There is an important restriction on the last two. For a file opened in text mode, Python allows only offsets counted from the beginning — reference point 0 — together with the single special case seek(0, 2), which jumps to the end. Attempting fobj.seek(5, 1) on a text file raises an error. Reference points 1 and 2 may be used freely with any non-zero offset only when the file has been opened in a binary mode, where one byte is one byte and no character decoding is involved.

Used on fares.txt, seek() lets the second line be read without touching the first.

>>> fobj = open("fares.txt", "r")
>>> fobj.seek(17)
17
>>> fobj.read()
'Route 05 fare 20'
>>> fobj.tell()
33
>>> fobj.seek(0)
0
>>> fobj.readline()
'Route 12 fare 15\n'
>>> fobj.close()

Notice that seek() itself returns the new offset, which is why the shell displayed 17 and then 0. Notice also the last two statements: after the whole file had been read, seek(0) made it readable again from the beginning. That is the remedy for the error met in Section 2.5, where a second read returned nothing because the pointer was already at the end.

a relative seek on a text file What students write:

fobj = open("fares.txt", "r")
fobj.read(5)
fobj.seek(3, 1)

What happens: the program stops with io.UnsupportedOperation: can't do nonzero cur-relative seeks. Text mode does not permit an offset counted from the current position. The fix: work out the absolute position and seek from the beginning — fobj.seek(fobj.tell() + 3) — or open the file in binary mode if genuine relative movement is needed.

Q. What happens if a program seeks past the end of the file and then reads?

A. No error is raised. The pointer is placed where it was asked to go, and reading from there returns an empty string, because there are no bytes at or after that position. Seeking beyond the end is legal; expecting to find data there is not.

The program below puts both methods together and prints the pointer position at each stage, so that the movement can be watched rather than imagined.

tracking the file pointer while reading Problem: Open a text file, display the position of the file pointer before reading, after reading one line, and after returning to the beginning, and show the first five characters of the file. Assumes: The file fares.txt exists in the current working directory and holds:

Route 12 fare 15
Route 05 fare 20
with open("fares.txt", "r") as fobj:
    print("Position at start :", fobj.tell())
    first = fobj.readline()
    print("First line        :", first, end="")
    print("Position now      :", fobj.tell())
    fobj.seek(0)
    print("Back to start     :", fobj.tell())
    print("First five chars  :", fobj.read(5))

Sample output:

Position at start : 0
First line        : Route 12 fare 15
Position now      : 17
Back to start     : 0
First five chars  : Route

Identifier description:

Identifier Type Purpose
fobj File object Link to fares.txt, opened for reading
first String The first line of the file, including its newline

Notes: The end="" on the second print() is needed because first already ends with a newline; without it the output would carry a blank line. The final read(5) returns five characters and not five bytes of some other kind only because every character in this file is a plain ASCII character occupying one byte.

the pointer position through the program above

Step Statement Offset before Offset after Printed
1 fobj.tell() 0 0 Position at start : 0
2 fobj.readline() 0 17 —
3 print(first, end="") 17 17 First line : Route 12 fare 15
4 fobj.tell() 17 17 Position now : 17
5 fobj.seek(0) 17 0 —
6 fobj.tell() 0 0 Back to start : 0
7 fobj.read(5) 0 5 First five chars : Route

Result: The offsets run 0, 17, 0, 5. The trace shows that reading moves the pointer and tell() does not, and that seek() moves it without reading anything.

Using fares.txt, write the statements that display only the word fare from the second line, by seeking to the right offset and reading exactly four characters. Work out the offset on paper first, counting the newline, and then check your answer by running the code.

2.7 Creating and Traversing a Text File

You now have every piece needed for real work on a text file: a way to open it in the right mode, to write to it, to read it back, and to move about inside it. This section puts those pieces together into complete programs of the kind you will write in the practical file.

Three tasks cover almost everything a text file is asked to do. The first is to create a file and fill it with data taken from the user. The second is to traverse it — to visit every line in turn and do something with each. The third is to do both through a single file object, which is where the file pointer stops being a technicality and becomes the thing that makes the program work.

Fig. 2.10 shows the shape common to the first two tasks.

Fig. 2.10

Fig. 2.10 – File writing and traversal flow.

2.7.1 Creating a File and Writing Data

Creating a file is not a separate operation in Python. Opening a file that does not exist, in "w", "w+", "a" or "a+" mode, creates it. Which of those modes is chosen decides the fate of any file that does exist under the same name: the two write modes empty it, the two append modes keep it.

The usual pattern is a loop that takes entries from the user until told to stop, writing each one as it arrives.

creating a text file from data typed by the user Input: Entries typed at the keyboard, and the name of the file to be created. Output: A text file holding one entry per line. Method:

1. Open the file in write mode, so that it is created if absent and emptied if present.
2. Repeat steps 3 to 5 until the user chooses to stop.
3. Read one entry from the keyboard.
4. Write the entry to the file, followed by a newline character.
5. Ask the user whether more data are to be entered.
6. Close the file.

Why it works: Each entry reaches the file with a newline after it, so the entries become separate lines and can later be read back one at a time. Because the file is opened once and closed once, the pointer simply advances from one entry to the next. Watch out: If the newline in step 4 is omitted, every entry is written immediately after the one before it and the whole file becomes a single long line — the file will look correct in size and be useless to read line by line. If the user stops at once, the file is left empty, and any earlier contents have already been destroyed by step 1.

creating a text file of visitor entries Problem: Accept entries from the user, one at a time, and store each on its own line of a text file. After each entry, ask whether more data are to be entered, and stop when the answer is n. Assumes: No file need exist beforehand; visitors.txt is created in the current working directory.

fobj = open("visitors.txt", "w")

while True:
    entry = input("Purpose of visit: ")
    fobj.write(entry + "\n")
    more = input("Any more entries (y/n): ")
    if more.lower() == "n":
        break

fobj.close()
print("Data saved in visitors.txt")

Sample output:

Purpose of visit: Meeting the class teacher
Any more entries (y/n): y
Purpose of visit: Delivering library books
Any more entries (y/n): n
Data saved in visitors.txt

Identifier description:

Identifier Type Purpose
fobj File object Link to visitors.txt, opened in write mode
entry String One entry typed by the user
more String The user’s answer to the question about further entries

Notes: The newline is added by the program, not by write(), which is why entry + "\n" appears rather than plain entry. Using more.lower() means that n and N are both accepted, so the program does not depend on the user’s use of the shift key. The loop is written as while True with a break because the decision to continue can only be made after at least one entry has been taken.

After that run, the file holds two lines:

Meeting the class teacher
Delivering library books

Q. The same program is run a second time. What happens to the two entries above?

A. They are destroyed. "w" empties the file when it is opened, so the second run starts from nothing. To add to an existing log rather than replace it, the mode must be "a", and only that one character of the program needs to change.

2.7.2 Traversing a File and Displaying Data

To traverse a file is to visit its lines one after another until there are none left. The end is detected by the value a reading method returns: readline() gives back the empty string once the file pointer has reached the end of the file, and an empty string is false in a condition.

That single fact is what makes the loop below terminate.

traversing a text file to the end Input: The name of an existing text file. Output: Each line of the file, displayed with its serial number, and the count of lines. Method:

1. Open the file in read mode.
2. Read one line from the file.
3. Repeat steps 4 to 6 as long as the line read is not empty.
4. Increase the count of lines by one.
5. Display the count and the line.
6. Read the next line from the file.
7. Close the file and display the total count.

Why it works: Every call to the reading method returns one line and moves the pointer past it, so step 6 always advances. When the pointer reaches the end of the file, the call returns an empty string, the condition in step 3 fails and the loop stops. Watch out: If step 6 is left out, the same line is tested for ever and the program never ends. If the file is empty, the very first read returns an empty string, the loop body never runs, and the count is correctly reported as zero.

displaying a text file line by line with a count Problem: Read a text file and display each line preceded by its serial number, then display the total number of lines. Assumes: The file visitors.txt exists in the current working directory and holds:

Meeting the class teacher
Delivering library books
fobj = open("visitors.txt", "r")
line = fobj.readline()
count = 0

while line:
    count = count + 1
    print(count, ":", line, end="")
    line = fobj.readline()

fobj.close()
print("Total entries:", count)

Sample output:

1 : Meeting the class teacher
2 : Delivering library books
Total entries: 2

Notes: The first readline() before the loop is what gives the condition something to test; this arrangement is called reading ahead, and it is the standard way to write a while loop over a file. The end="" prevents a blank line after each entry, since every line read already carries its own newline.

the traversal loop on the two-line file visitors.txt

Pass line at the test Condition count after step 4 Printed Offset after the next read
1 Meeting the class teacher\n True 1 1 : Meeting the class teacher 26
2 Delivering library books\n True 2 2 : Delivering library books 51
3 '' False — — 51

Result: The loop runs twice and stops on the third test, and the program prints a total of 2. The trace shows that the empty string at the end of file is not a line to be processed but the signal that processing is over.

a traversal loop that never ends What students write:

line = fobj.readline()
while line:
    print(line, end="")

What happens: the first line is printed again and again without stopping, because nothing inside the loop changes line. The fix: read the next line as the last statement of the loop body — line = fobj.readline() — so that each pass moves the file pointer forward and the condition can eventually become false. A for line in fobj: loop avoids the problem altogether, since the advancing is done for you.

2.7.3 Reading and Writing Through a Single File Object

So far, writing and reading have used two file objects: the file was written, closed, and opened again to be read. A single file object can do both, if the mode allows it — "w+", "r+" or "a+".

Doing so raises a question the earlier programs never had to face. After the writing is finished, the file pointer stands at the end of the file, so a read at that moment returns nothing at all. The pointer must be brought back to the beginning first, and seek(0) is what does it.

Fig. 2.11

Fig. 2.11 – Using seek(0) to read after writing.

writing and then reading a text file through one file object Problem: Accept a stated number of remarks from the user and write them to a text file, then, using the same file object, display the byte position reached and the full contents of the file. Assumes: No file need exist beforehand; feedback.txt is created in the current working directory.

fobj = open("feedback.txt", "w+")

count = int(input("How many remarks are to be entered? "))
for i in range(count):
    remark = input("Remark " + str(i + 1) + ": ")
    fobj.write(remark + "\n")

print("Byte position after writing:", fobj.tell())
fobj.seek(0)
print("Contents of the file:")
print(fobj.read())
fobj.close()

Sample output:

How many remarks are to be entered? 2
Remark 1: The bus reached on time
Remark 2: Water cooler needs repair
Byte position after writing: 50
Contents of the file:
The bus reached on time
Water cooler needs repair

Identifier description:

Identifier Type Purpose
fobj File object Link to feedback.txt, opened in "w+" mode for writing and reading
count Integer Number of remarks to be entered
i Integer Loop counter, used to number the prompts
remark String One remark typed by the user

Notes: The position 50 is the total number of characters written — 24 for the first remark with its newline and 26 for the second. The blank line at the end of the output is not an error: read() returned a string ending in a newline, and print() added one more. Removing it is a matter of writing print(fobj.read(), end="").

Take the program above and delete the fobj.seek(0) line. Predict what the output will be before you run it, then run it and check. Explain in one sentence why the contents disappeared without any error being reported.

2.8 The Pickle Module

Everything written so far has gone into a text file, and a text file can hold only characters. That is a real limitation, and it shows up the moment a program has something more structured than characters to store.

Suppose a program holds a list of three sports events, each event itself a list of an event number, a name and a count of entries. Write that list to a text file and what reaches the disk is its printed appearance — brackets, commas, quotation marks and all. Read it back and you get one long string. The list has become a picture of a list, and rebuilding the real thing from that picture means picking the string apart character by character.

>>> record = [1, "100 m Race", 24]
>>> str(record)
"[1, '100 m Race', 24]"

Python offers a better answer. The pickle module can take a Python object as it stands — with its type, its structure and its nesting intact — turn it into a stream of bytes, and store that stream in a binary file. Later the same module reads the bytes back and rebuilds the object exactly as it was.

Q. Why not simply write str(record) to a text file and use it as it is?

A. Because what comes back is a string, not a list. data[0] on that string gives the character [, not the number 1, and there is no arithmetic to be done with it. Restoring the list would mean parsing the string yourself, and any comma inside a name would break the parsing. The structure has to be stored, not just its appearance.

2.8.1 Pickling and Unpickling

Turning an object in memory into a stream of bytes is called serialization, and in Python it is called pickling. The reverse — rebuilding the object from those bytes — is deserialization, or unpickling.

NoteDefinition

Pickling is the process of converting a Python object into a byte stream so that it can be stored in a binary file or transmitted, and unpickling is the reverse process, which reconstructs the original object from that byte stream.

The byte stream produced is not readable text and is not meant to be. It is a compact record of what the object is — that it is a list, that its first element is the integer 1, that its second is a string of ten characters, and so on — laid out in a form that pickle knows how to read back.

Serialization Statement: An object in memory can be represented as a sequence of bytes from which an equivalent object can later be reconstructed. In words: Turn the object into bytes to store it, and turn the bytes back into the object to use it. Why it holds: An object is fully described by its type and the values it contains; if both are recorded in an agreed format, nothing is lost and the description is enough to rebuild it. Used for: Saving the state of a running program, storing records of mixed types in a file, and moving objects between programs. Watch out: The format is specific to Python, so a pickled file cannot be read by a program written in another language, and it cannot be inspected in a text editor. A file pickled by a program you did not write should not be loaded, because unpickling reconstructs whatever the file describes; open only the binary files your own programs created.

The name is borrowed from the kitchen. Pickling preserves food by putting it in a form in which it keeps for months and can be brought out unchanged. The module does the same for an object: it preserves it in a form that keeps, and restores it on demand.

The module is used through two functions, dump() for writing and load() for reading, and it must be imported before either is used.

import pickle

Because a pickled object is a stream of bytes rather than characters, the file it goes into must be opened in a binary mode — "wb" to create it, "ab" to add to it, "rb" to read it. This is the point at which the binary modes of Table 2.4 stop being a list to learn and become something you actually need.

Fig. 2.12

Fig. 2.12 – Serialization and deserialization of a Python object.

Table 2.15 – Storing a list in a text file and in a binary file | Feature | Text file | Binary file with pickle | |—|—|—| | What is stored | The characters of the printed form of the object | A byte stream describing the object itself | | Conversion needed before writing | str() on every value that is not a string | None; the object is passed as it is | | What comes back on reading | A string | An object of the original type | | Rebuilding the structure | Must be done by the program, character by character | Done by pickle.load() | | Readable in a text editor | Yes | No | | Mode used | "w", "a", "r" | "wb", "ab", "rb" |

2.8.2 The dump() Method

The dump() function pickles one object and writes it to a binary file.

writing an object to a binary file

pickle.dump(data_object, file_object)

Parts: data_object is the Python object to be stored — a list, a tuple, a dictionary, a number or a string; file_object is a file open in a binary writing mode. Returns / Effect: returns nothing; the byte stream for the object is written at the current position in the file. Example:

pickle.dump([1, "100 m Race", 24], bfile)

One call stores one object. To store several records, dump() is called once for each, and the byte streams are laid down one after another in the order they were written, as Fig. 2.13 shows. The file has no line structure and no separators — each pickled object carries within itself the information about where it ends.

Fig. 2.13

Fig. 2.13 – Sequential binary records without separators.

writing records of sports events to a binary file Problem: Store three event records, each holding an event number, an event name and the number of entries, in a binary file, one record per dump() call. Assumes: No file need exist beforehand; events.dat is created in the current working directory.

import pickle

events = [[1, "100 m Race", 24], [2, "Long Jump", 18], [3, "Relay", 32]]

with open("events.dat", "wb") as bfile:
    for record in events:
        pickle.dump(record, bfile)

print("Records written:", len(events))

Sample output:

Records written: 3

Identifier description:

Identifier Type Purpose
events List of lists The three records to be stored
bfile File object Link to events.dat, opened in binary write mode
record List The record being written in the current pass

Notes: Each record is dumped separately rather than dumping the whole list in one call. Both work, but separate records can later be read one at a time, which is what a search program needs, and new records can be appended in "ab" mode without disturbing the ones already stored. Nothing is printed from inside the loop, because dump() returns nothing to print.

opening a binary file in text mode for pickling What students write:

import pickle
bfile = open("events.dat", "w")
pickle.dump([1, "100 m Race", 24], bfile)

What happens: the program stops with TypeError: write() argument must be str, not bytes. A file opened in text mode expects characters, and pickle produces bytes. The fix: open the file in a binary mode — open("events.dat", "wb") for writing and open("events.dat", "rb") for reading. The b is not optional here; it is what tells Python that bytes, not characters, are to pass through.

2.8.3 The load() Method

The load() function reads one pickled object from a binary file and returns it, rebuilt.

reading an object from a binary file

store_object = pickle.load(file_object)

Parts: file_object is a file open in a binary reading mode. Returns / Effect: returns the next object stored in the file, of the same type it had when it was pickled, and moves the file pointer past that object. Example:

record = pickle.load(bfile)

Each call returns the next object, in the order the objects were written. When there is nothing left, load() does not return an empty value the way readline() does — it raises an EOFError. That is the signal that the file has been read completely, and a program that reads until the end must be prepared to catch it.

reading all records from a binary file Input: The name of a binary file holding pickled records. Output: Every record in the file, displayed in the order it was written. Method:

1. Import the pickle module.
2. Open the file in binary read mode.
3. Repeat steps 4 to 5 for ever.
4. Load the next object from the file.
5. Display the object.
6. When loading raises the end-of-file error, stop repeating and close the file.

Why it works: Each load moves the file pointer past one complete record, so the repetition always advances, and the error raised at the end guarantees that it stops. The order of the records is the order in which they were dumped. Watch out: The loop has no counted limit, so the error must be caught; without a try block the program ends with an unhandled EOFError. An empty file raises the error on the very first load, and the correct result is then “no records”, not a crash.

reading all records from a binary file Problem: Read every record stored in a binary file and display it, then display the number of records found. Assumes: The file events.dat exists in the current working directory and holds the three records written by the previous program.

import pickle

count = 0

try:
    with open("events.dat", "rb") as bfile:
        while True:
            record = pickle.load(bfile)
            count = count + 1
            print(record)
except EOFError:
    pass

print("Total records read:", count)

Sample output:

[1, '100 m Race', 24]
[2, 'Long Jump', 18]
[3, 'Relay', 32]
Total records read: 3

Notes: What is printed is a list, complete with brackets and quotation marks, because a list is exactly what came back — this is the whole point of pickling. The except EOFError block does nothing but stop the loop, which is why its body is pass. The file is closed correctly even though the loop is left by an exception, because the with clause closes it on the way out.

Q5. A list or a dictionary exists only while the program runs. How can one be stored in a file and brought back later exactly as it was?

A. By pickling it. The pickle module converts the object into a byte stream that records not only its values but its type and structure, and dump() writes that stream into a binary file. Later, load() reads the stream back and rebuilds an object of the same type, so a list returns as a list and a dictionary as a dictionary, ready to be indexed and used. Writing the object to a text file would store only its printed appearance, and reading that back would give a string with the structure lost.

2.8.4 File Handling Using the Pickle Module

The two functions together support the operations that any record file needs: adding records, displaying them all, and finding one. Searching is the operation that shows most clearly why the records were dumped separately.

searching for a record in a binary file Input: A binary file of pickled records, each record a list whose first element is a key, and the key value to be found. Output: The matching record, or a message that no record matched. Method:

1. Set a flag to record that nothing has been found yet.
2. Open the file in binary read mode.
3. Load the next record from the file.
4. If its first element equals the key, display the record, set the flag and stop searching.
5. Otherwise return to step 3.
6. When loading raises the end-of-file error, stop searching.
7. If the flag is still unset, report that no record matched.

Why it works: The records are examined in the order they were stored, and the search either finds a match and stops, or reaches the end of file, at which point every record has been examined and none matched. Watch out: The search must report failure as well as success; a program that prints nothing when the key is absent leaves the user unable to tell a missing record from a broken program. Since the records are read from the start each time, the whole file may have to be examined for a key that is not there.

searching a binary file for a record by its key Problem: Ask the user for an event number and display the name and the number of entries for that event, or a message if no such event is stored. Assumes: The file events.dat exists in the current working directory and holds the three records written earlier in this section.

import pickle

target = int(input("Enter the event number to search: "))
found = False

try:
    with open("events.dat", "rb") as bfile:
        while True:
            record = pickle.load(bfile)
            if record[0] == target:
                print("Event   :", record[1])
                print("Entries :", record[2])
                found = True
                break
except EOFError:
    pass

if not found:
    print("No event with that number")

Sample output:

Enter the event number to search: 2
Event   : Long Jump
Entries : 18

Identifier description:

Identifier Type Purpose
target Integer The event number being searched for
found Boolean Records whether a matching record has been seen
bfile File object Link to events.dat, opened in binary read mode
record List The record loaded in the current pass

Notes: record[0] can be compared with an integer directly, without any conversion, because the record came back as a list of the original types. Had these records been kept in a text file, the same comparison would have needed the line to be split and its first field passed through int(). The break stops the search as soon as a match is found; the found flag is what allows the program to say something sensible when the loop ends without a match.

Change the mode in the writing program from "wb" to "ab" and run it a second time, then run the reading program again. Note how many records are now displayed, and explain in one sentence why the count changed. Then predict what the search program would print if the file contained two records with the same event number.

2.9 Summary

  1. A variable lives only while the program runs, and the memory holding it is volatile, so its value is lost when the program ends or the power goes off. Data that must survive are written to secondary storage, and this permanence is what the whole chapter is built on.

  2. A file is a named location on a secondary storage medium where data are stored permanently for later access. The name and the path are what allow a program to ask for one particular collection of bytes rather than any other, and a file kept outside the current working directory must be named by its absolute path.

  3. Every file is stored as a sequence of bytes. What makes a file a text file or a binary file is not the storage but the agreement about what those bytes mean, and which software is prepared to honour that agreement.

  4. In a text file each byte, or small group of bytes, is the code of a character in a scheme such as ASCII or Unicode, so the contents can be read by any text editor. In a binary file the bytes carry the data directly in the format of the application that created them, and are not readable as characters.

  5. A text file has lines only because newline characters were placed in it. The end of line character, \n by default in Python, is a single character occupying one byte, and it is counted like any other character.

  6. A file is opened with open(), which is given the file name and the access mode and returns a file object. The file object is the program’s only link to the file, and it keeps track of the file name, the mode and the current position within the file.

  7. The access modes are r, r+, w, w+, a and a+ for text files, and rb, rb+, wb, wb+, ab and ab+ for binary files. The letter says what the file may be used for, + adds the other operation, and b says that the file is to be handled as bytes rather than as text.

  8. The mode also decides the fate of an existing file and the starting position of the file pointer. The read modes leave the contents untouched and raise an error if the file is absent; the write modes create the file if it is absent and empty it if it is present; the append modes create it if absent, keep the contents, and begin at the end.

  9. A file object carries three useful attributes: name, mode and closed. The last of these is False while the file is open and True once it has been closed.

  10. Data written to a text file are first collected in a buffer in memory and reach the disk only when the buffer is emptied. Closing the file with close() flushes that buffer and releases the resources the operating system had reserved, which is why a file must be closed and not merely finished with.

  11. The with clause opens a file for the length of a block and closes it automatically on leaving the block, whether the block ends normally or because an error was raised. It removes the possibility of forgetting to close a file.

  12. Writing is done by write(), which takes one string and returns the number of characters written, and by writelines(), which takes an iterable of strings and returns nothing. Neither adds a line break; a newline appears in the file only if the program put one in the string.

  13. A text file holds characters and nothing else, so a number must be converted with str() before it is written, and a value read back from a text file is a string until it is converted again.

  14. Reading is done by read(), readline() and readlines(), which read the same data but hand back different shapes: one string, one line as a string, and a list of one string per line. Each of them keeps the newline characters, because the newlines are part of the stored data.

  15. The end of file is the position after the last character. A reading method called there returns an empty string, and readlines() returns an empty list, and that emptiness is how a program knows the file has been read completely.

  16. A file object can be used directly in a for loop, which yields one line at a time and never holds more than one line in memory. This is the usual way to visit every line of a file of any size.

  17. The file pointer position, measured in bytes from the beginning of the file, is called the offset. tell() reports the current offset without changing anything, and seek() moves the pointer so that the next operation acts at a chosen position.

  18. For a file opened in text mode, seek() counts offsets from the beginning of the file; offsets counted from the current position or from the end require a file opened in binary mode. Moving the pointer back to the start with seek(0) is what allows a file to be read after it has been written through the same file object.

  19. Traversing a file means visiting its lines until none are left. A while loop over a file reads one line before the loop and one at the end of every pass, so that the pointer always advances and the empty string at the end of file stops the loop.

  20. Pickling is the conversion of a Python object into a byte stream so that it can be stored in a binary file; unpickling is the reverse, which rebuilds the object from those bytes. Because the type and structure are recorded and not just the printed appearance, a list comes back as a list and a dictionary as a dictionary.

  21. The pickle module provides dump() for writing an object to a binary file and load() for reading the next object back. The file must be opened in a binary mode, one call to dump() stores one object, and load() raises an EOFError when there are no objects left, which a program reading to the end must catch.

2.10 Key Terms

  1. Absolute path — the complete location of a file, written from the root of the file system or from the drive letter down to the file name, so that the file is identified independently of where the program is running.

  2. ASCII — American Standard Code for Information Interchange; an encoding scheme that gives every English letter, digit and common punctuation mark a number from 0 to 127.

  3. Binary file — a file whose bytes represent the data directly, in the format of the application that created them, and which therefore cannot be read as characters by a text editor.

  4. Byte stream — the sequence of bytes produced when an object is serialized, from which the object can later be rebuilt.

  5. Current working directory — the folder a program treats as its own, and in which a file named without any path is looked for.

  6. Data file — a file holding the information a program works on, as distinct from a file holding the program itself.

  7. Deserialization — the rebuilding of an object from the byte stream that represents it; in Python, unpickling.

  8. EOF — end of file; the position immediately after the last character of a file, at which a reading method returns an empty string.

  9. EOL — end of line; the special character that marks where one line of a text file ends, \n by default in Python.

  10. File — a named location on a secondary storage medium where data are stored permanently for later access.

  11. File access mode — the string passed to open() that says whether a file may be read, written or appended to, whether it is to be treated as text or as bytes, and what is to happen to any existing contents.

  12. File name extension — the part of a file name after the dot, which announces the kind of data the file is expected to hold, such as .txt, .py or .dat.

  13. File object — the object returned by open(), through which all operations on an open file are performed, and which holds the file name, the mode and the current position in the file.

  14. File pointer — the marker kept by a file object showing where in the file the next read or write will act; it advances past whatever is read or written.

  15. Offset — the position of the file pointer, counted in bytes from the beginning of the file, with 0 meaning the position before the first byte.

  16. Persistence — the property of data that continue to exist after the program that produced them has ended and after the machine has been switched off.

  17. Pickling — the conversion of a Python object into a byte stream so that it can be stored in a binary file; the Python name for serialization.

  18. RAM — random access memory; the computer’s primary memory, which holds a program and its data while it runs and loses its contents when the power goes off.

  19. Secondary storage — a storage device such as a hard disk, solid-state drive or pen drive, which retains its contents when the power is switched off.

  20. Serialization — the process of representing an object in memory as a sequence of bytes that can be stored or transmitted.

  21. Text file — a file in which the data are stored as characters, each represented by its code in a scheme such as ASCII or Unicode, so that the contents can be read by any text editor.

  22. Unicode — an encoding scheme that extends character codes to the scripts of the world, and which Python 3 uses by default for text.

  23. Unpickling — the reconstruction of a Python object from the byte stream stored in a binary file; the Python name for deserialization.

  24. Volatile memory — memory whose contents survive only while it is powered, so that everything held in it is lost when the machine is switched off.

2.11 Points to Ponder

  1. Every file on a disk is only a sequence of bytes, yet Python asks you to decide at the moment of opening whether it is to be treated as text or as bytes. Why should that decision be forced so early, rather than at the moment the data are actually read?

  2. Opening a file in "w" mode destroys everything in it without any warning and without any way to undo the loss. Would Python be a better designed language if it refused to do this silently, and what convenience would be given up in exchange?

  3. A newline is stored as an ordinary character, exactly like a comma or the letter A. In what sense, then, does a text file really “have lines” at all, and where do those lines exist — in the file, in the software that reads it, or only in the reader’s mind?

  4. readline() announces the end of a file by returning an empty string, while pickle.load() announces it by raising an error. Both are part of the same language. What might justify answering the same question in two such different ways?

  5. Data written to a file wait in a buffer in memory until the file is closed. This makes writing much faster and makes losing the last data possible. Where should the balance between speed and safety be struck, and should the programmer or the language be the one to strike it?

  6. A pickled file holds a Python object whole, but cannot be read by a program written in any other language and cannot be inspected in a text editor. What exactly is being traded away for that convenience, and when would the trade be a bad one?

2.12 Objective Questions

  1. Which mode opens an existing text file for both reading and writing without erasing what it already contains?
    1. "w"
    2. "w+"
    3. "r+"
    4. "a" [1 mark]
  2. Which of the following is not a valid file access mode in Python?
    1. "ab+"
    2. "rw"
    3. "wb"
    4. "r+" [1 mark]
  3. What does readlines() return when it is called on a file whose pointer is already at the end of the file?
    1. An empty string
    2. An empty list
    3. None
    4. An EOFError [1 mark]
  4. What is the value returned by write() on a successful call?
    1. True
    2. The string that was written
    3. The number of characters written
    4. Nothing [1 mark]
  5. Assertion (A): A file opened in "a" mode never loses the data it already holds. Reason (R): In append mode the file pointer is placed at the end of the file when the file is opened.
    1. Both A and R are true and R is the correct explanation of A.
    2. Both A and R are true but R is not the correct explanation of A.
    3. A is true but R is false.
    4. A is false but R is true. [1 mark]
  6. Assertion (A): A binary file must be opened in a binary mode before pickle.dump() can write to it. Reason (R): pickle.dump() produces a byte stream, and a file opened in text mode accepts only strings.
    1. Both A and R are true and R is the correct explanation of A.
    2. Both A and R are true but R is not the correct explanation of A.
    3. A is true but R is false.
    4. A is false but R is true. [1 mark]
  7. Match each method in Column I with what it returns, given in Column II.
Column I Column II
a. read() i. A list of strings
b. readline() ii. An integer offset
c. readlines() iii. A single string holding the whole file
d. tell() iv. A single string holding one line

[1 mark]

  1. Fill in the blank: the method used to place the file pointer at a chosen position in a file is ____________. [1 mark]

  2. State whether the following is true or false: a newline occupies two bytes in a text file because it is written as \n. [1 mark]

  3. What will be displayed by the second statement of this shell session?

>>> fobj = open("data.txt", "w")
>>> fobj.write("Class XII\n")

[1 mark]

  1. Write the full form of: (a) EOL (b) EOF (c) ASCII (d) RAM. [1 mark]

2.13 Check Your Concepts

  1. Define pickling, and name the two functions the pickle module provides for working with binary files. [2 marks]

  2. What is a file pointer? State where it is placed at the moment a file is opened in "r" mode and in "a" mode. [2 marks]

  3. Differentiate between write() and writelines() in two points. [2 marks]

  4. Differentiate between a text file and a binary file, giving one example of each. [3 marks]

  5. Write the Python statement to open each of the following, and name the mode used: (a) a text file log.txt in append and read mode, (b) a binary file stock.dat in read only mode, (c) a text file data.txt for reading and writing without erasing its contents. [3 marks]

  6. A student writes the marks of a class to a text file and reads them back to find their total, but the addition fails. Explain, with a short illustration, why this happens and what must be done at each end. [3 marks]

  7. Explain the working of readline(), stating clearly what it returns when the end of the file has been reached, and write a loop that uses it to display an entire file line by line. [4 marks]

  8. Why must a file be closed after use? Answer with reference to buffering, and state two things the with clause does that an explicit close() call may fail to do. [4 marks]

  9. Explain seek() and tell(), giving the syntax of each, and describe how they are used to read a file that has just been written through a file object opened in "w+" mode. State one restriction that applies to seek() on a file opened in text mode. [5 marks]

  10. Write a function count_words(fname) that returns the number of words in the text file whose name is passed to it. State the mode in which you open the file and why, and explain what your function returns for a file that is empty. [5 marks]

2.14 Output & Debugging

The next two questions refer to the text file marks.txt, whose contents are:

Anita 78
Balbir 85
Chetan 62
  1. Predict the output of the following fragment, and state the position of the file pointer when the fragment ends.
fobj = open("marks.txt", "r")
print(fobj.read(6))
print(fobj.tell())
fobj.close()

[2 marks]

  1. State exactly what this fragment displays, and explain in one line why the display begins where it does.
fobj = open("marks.txt", "r")
fobj.seek(10)
print(fobj.readline())
fobj.close()

[2 marks]

  1. The following fragment stops with an error. Name the error, explain in one line why it occurs, and rewrite the fragment correctly.
fobj = open("count.txt", "w")
total = 100
fobj.write(total)
fobj.close()

[2 marks]

  1. This fragment is meant to count the lines of a text file, but it does not stop running. Identify the single fault, correct it, and state what the corrected fragment prints for marks.txt shown above.
fobj = open("marks.txt", "r")
line = fobj.readline()
count = 0
while line:
    count = count + 1
fobj.close()
print(count)

[3 marks]

  1. Predict the output of the following program, and state why the value printed by type() matters.
import pickle

data = [10, 20, 30]
fobj = open("nums.dat", "wb")
pickle.dump(data, fobj)
fobj.close()

fobj = open("nums.dat", "rb")
x = pickle.load(fobj)
fobj.close()
print(type(x), x[1] + 5)

[3 marks]

2.15 Case-Based Questions

  1. A school library keeps a record of the books it has issued in a text file named issued.txt. Each line holds the title of a book followed by the number of days for which it has been issued. The file currently holds:
The Guide 14
Panchatantra 7
Train to Pakistan 21
Indian Birds 10
*a. Write the statement that opens this file so that a new record can be added at the end without disturbing the existing records, and the statement that adds the record for `Wings of Fire 14`. [2 marks]*
*b. Write a function that displays only those titles that have been issued for more than ten days, along with the number of days. [3 marks]*
*c. The librarian's assistant runs a program that opens the file in `"w"` mode and then stops it before anything is written. State what the file now contains and give the reason. [1 mark]*

[6 marks]

  1. A bus depot stores the details of its routes in a binary file named routes.dat. Each record is written with a separate call to pickle.dump() and is a list of the form [route_number, destination, seats_booked]. The file currently holds three records:
[12, 'Amber Fort', 38]
[5, 'Railway Station', 44]
[9, 'Civil Lines', 27]
*a. Write the statements that open the file so that a fourth record can be added without disturbing the three already stored, and that write the record `[7, 'Bus Stand', 31]` to it. [2 marks]*
*b. Write a program that reads the file and displays the destination and the seats booked for every route on which more than thirty seats have been booked. Your program must stop cleanly when the file has been read completely. [3 marks]*
*c. The depot clerk asks why these records were not simply kept in a text file. Give two reasons drawn from the way the records are used in part (b). [2 marks]*

[7 marks]

2.16 Programming Practice

  1. Write a complete program that accepts sentences from the user one at a time and stores each on its own line of a text file named diary.txt, stopping when the user types END. The word END must not be stored in the file. Submit the program together with an identifier description table. [4 marks]

  2. A text file article.txt holds several lines of ordinary English. Write a function that reads the file and displays the number of uppercase letters, the number of lowercase letters and the number of digits in it. Submit the function and state what it displays for a file that contains no digits at all. [4 marks]

  3. Write a function copy_lines(source, target, word) that copies into the file target only those lines of the file source that contain the given word, and returns the number of lines copied. State the mode in which each of the two files is opened. Submit the function only. [5 marks]

  4. A shop keeps its stock in a binary file items.dat, each record written as a list of the form [item_code, item_name, quantity] with a separate call to pickle.dump(). Write a complete program that offers the user two choices — to add a record entered from the keyboard, and to display every record in the file — and carries out the chosen task. Sample input for the first choice is an item code of 104, an item name of Chart Paper and a quantity of 60. Submit the program with an identifier description table. [5 marks]