Opening and Closing Files Safely with the with Statement
read() loads an entire file into a single string, including all newline characters, and exhausts the file handle resource after one call.
Try it: Reading and Writing Text Files
How open() returns a file object with a position pointer, how read(), read(n), readline(), readlines() and for line in fh advance it (and return '' at the end), how 'w' and 'a' modes change the file, and why a closed file (after close() or a with block) can no longer be read.
How it works
- open(name, 'r') starts the pointer at 0; 'w' empties the file first; 'a' starts at the end.
- read() returns everything from the pointer to the end; read(n) at most n characters; readline() up to and including the next \n; each moves the pointer past what it returned.
- At the end of the file every read returns '' (an empty string), which is how loops know to stop; for line in fh reads line by line the same way.
- write() text waits in a buffer and reaches the file when it is closed (or the program ends); reading a 'w'/'a' file or writing an 'r' file raises io.UnsupportedOperation.
- close(), or leaving a with block, closes the file; any further read raises ValueError: I/O operation on closed file.
Default run (10 steps): Ready: notes.txt holds 4 lines (51 characters). Nothing is open yet. … Finished. notes.txt now holds "From: stephen\nSubject: meeting\nFrom: louis\nThanks!\n".
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. One virtual text file with \n line endings (as on Linux/macOS), positions counted in characters (ASCII only), and a write buffer that is only flushed by close() or the end of the program.
Loading the simulation…
The First Read Changes the Handle
A file can look like a permanent document, so it is tempting to expect read() to behave like a repeatable request for the same contents. It does not. A file handle keeps a position pointer, like a bookmark. Opening the file starts that pointer at the beginning. A call to read() takes the data from the current position through the end of the file, then leaves the pointer at the end.
What do you think happens?
After the first read() has consumed the complete file, what will a second read() return?
Reveal answer
An empty string
The first call moves the file handle's position pointer to the end. The second call begins at that end position, where no data remains.
Loading the Complete Contents
read() loads the entire file into one string. That string includes the file's newline characters, so multiple lines remain represented as one string containing line breaks. To preserve the result, assign it to a variable immediately. Once the result has been stored, your program can work with that string without calling read() again.
with open("notes.txt") as file_handle:
contents = file_handle.read()
print(contents)- 1The file is opened and given the name
file_handleinside the with block. - 2The call to
read()starts at the handle's current position and loads the remaining contents into contents. - 3Because this is the first complete read, the handle reaches the end of the file.
- 4
print()uses the already stored string rather than callingread()a second time.
- The with statement provides the context for using the file handle.
read()loads the complete file into contents.- The newline characters from the file remain in the returned string.
- The stored contents variable can be used after
read()has finished.
First line
Second line
Third lineTracing Two Calls
Why the Second Result Is Empty
A file contains the text A, B, and C on separate lines. What do first_read and second_read contain after two calls to read()?
- 1Open the file
The file handle's position pointer starts at position 0, the beginning of the file.
- 2Call read() first
The method reads from the current position through the end, so
first_readreceives the complete contents, including the newline characters. - 3Call read() second
The pointer is now at the end of the file. There is no remaining data, so
second_readreceives an empty string.
first_read contains the complete file contents. second_read contains an empty string.
'A\nB\nC'
''The with Block Lifecycle
The with statement places file use inside a managed block. The file handle is available while the block runs, and the file resource is closed when execution leaves the block, including when execution leaves because an error occurs. This makes the with statement the safe structure for the open, read, and close lifecycle.
Use the with statement for file handling and assign the result of read() immediately. This combines safe resource management with preservation of the data before the file handle reaches the end of the file.
Memory and File Size
read() is simple and fast, but it loads the complete file into one string. The file therefore needs to fit entirely in available RAM. A 100 MB file requires at least 100 MB of free memory for its contents, and a 1 GB file requires 1 GB. These requirements can make read() impractical for large files.
Selecting a Reading Strategy
| Situation | Recommended strategy | Reason |
|---|---|---|
| The complete file fits comfortably in available RAM | read() | The complete contents can be loaded into one string. |
| The file is large compared with available RAM | Loop-based reading | The file can be processed incrementally instead of loading it all at once. |
| The same handle has already completed read() | Use the stored variable | The pointer is at the end, so another read() returns an empty string. |
A practical guide to choosing and using read().
Mistakes with read()
Common mistake Expecting a second read() to return the complete file again.
What it looks likefirst = file_handle.read() second = file_handle.read()Why it failsThe first call moves the position pointer to the end of the file.
Do this insteadStore the first result and reuse that variable when you need the contents again.
Common mistake Calling read() repeatedly instead of preserving its result.
What it looks likeprint(file_handle.read()) print(file_handle.read())Why it failsThe second call starts at the end and returns an empty string.
Do this insteadAssign the result once, then use the assigned string.
Common mistake Using read() for a file that cannot fit in available RAM.
What it looks likecontents = file_handle.read()Why it failsread()loads the entire file into one string, so the complete file must fit in memory.Do this insteadUse loop-based reading for larger files to manage memory efficiently.
Practice the Decision
A file is 1 GB, and only a smaller amount of free RAM is available for the program. Should the program use read() to load the complete file into one string, or should it use loop-based reading? Explain your choice using both the file handle's position behavior and the memory requirement.
Hints
read()loads the complete file into one string.- A 1 GB file requires 1 GB of free memory for its contents.
- Loop-based reading is recommended for larger files.
A small file fits comfortably in available RAM. Write a short file-reading pattern that opens the file with a with statement, assigns read() to a variable immediately, and prints the stored contents without calling read() a second time.
Hints
- Place the file operation inside a with block.
- Assign
file_handle.read()to a variable. - Print the variable rather than calling
read()again.
Key Takeaways
- 1
read()loads the complete file, including newline characters, into one string. - 2The file handle has a position pointer that moves to the end after a complete
read(). - 3A later
read()on the same handle returns an empty string because no data remains at the pointer's position. - 4Assign the result of
read()immediately and reuse the stored variable. - 5Use
read()when the complete file fits in available RAM; use loop-based reading for larger files.
At a glance
- A file handle maintains a position pointer, and
read()consumes data from that position to the end. - After one complete
read(), a second call returns an empty string because the pointer is already at the end. - Store the result of
read()immediately so the data remains available without another read. - Because
read()loads the entire file into memory, use it for files that fit in available RAM and choose loop-based reading for larger files. - The with statement provides a managed context for using and closing the file resource.