March 10, 2026 · All About CS
File Handling in Python — Part 2: Appending, Deleting, the with Statement, and File Pointers
Complete your Python file handling knowledge — learn how to append data, safely delete files with the os module, use the with statement as a context manager, and understand how the file pointer drives every read and write operation.
File Handling in Python — Part 2: Appending, Deleting, the with Statement, and File Pointers
In Part 1, we covered the essentials: why files matter, how open() works, and how to create, write, and read files. This part completes the picture with the remaining operations — appending and deleting — plus two crucial concepts every Python developer needs: the with statement and the file pointer.
Key Takeaways
- Append mode (
"a") adds data at the end of a file without erasing what's already there — unlike write mode which overwrites. - Deleting files requires Python's built-in
osmodule and itsos.remove()function. - Always use
os.path.exists()before reading or deleting a file to avoid aFileNotFoundError. - The
withstatement closes files automatically — even if an error occurs — and is the recommended approach in all professional Python code. - The file pointer is the internal cursor that determines where every read and write operation begins.
Appending to a File
So far, write mode ("w") has been our go-to for putting data into files. But every time you use it, the file's existing content is wiped out. Append mode ("a") solves this — it opens the file and positions the cursor at the very end, so new data is added after everything already there:
my_file = open("stored_files/program_data.txt", "a")
my_file.write("\nHere goes some more data!")
my_file.close()If program_data.txt already contained:
Hello There! Program data stored successfully! 1
Hello There! Program data stored successfully! 2After running the above, it now contains:
Hello There! Program data stored successfully! 1
Hello There! Program data stored successfully! 2
Here goes some more data!The original content is untouched. Every subsequent run of the same script adds another line below.
Write vs. Append — The Critical Difference
This distinction trips up almost every beginner at least once:
| Mode | What happens to existing content | File pointer starts at |
|---|---|---|
"w" | Erased — completely overwritten | Beginning of file |
"a" | Preserved — new data added after it | End of file |
Run a program with "w" mode ten times: you'll only ever see the output from the most recent run. Run it with "a" mode ten times: every run's output is stacked in the file.
# Demonstrating the difference:
# Write mode — only the last run survives
for i in range(3):
with open("write_demo.txt", "w") as f:
f.write(f"Run #{i + 1}\n")
# File contains only: "Run #3"
# Append mode — every run is preserved
for i in range(3):
with open("append_demo.txt", "a") as f:
f.write(f"Run #{i + 1}\n")
# File contains:
# Run #1
# Run #2
# Run #3When to use append vs. write: Use "w" when you want to produce a fresh file each run — reports, exports, rendered templates. Use "a" when you want to accumulate data over multiple runs — logs, audit trails, data collection scripts.
Deleting Files with the os Module
Python's built-in open() handles creating, reading, writing, and appending — but not deletion. For that, you need the os module, which is part of Python's standard library (no installation required):
import os
os.remove("stored_files/program_data.txt")After this runs, the file is permanently removed from disk.
Defensive Deletion — Avoiding FileNotFoundError
If the file doesn't exist when os.remove() is called, Python raises a FileNotFoundError. The fix is to check first with os.path.exists():
import os
file_path = "stored_files/program_data.txt"
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleted: {file_path}")
else:
print(f"File not found — nothing to delete.")Now the program behaves gracefully whether or not the file exists.
Apply the same check before reading. Attempting to open a non-existent file in "r" mode raises the same FileNotFoundError. Use os.path.exists() before any read operation on a file whose existence you can't guarantee:
import os
if os.path.exists("stored_files/program_data.txt"):
with open("stored_files/program_data.txt", "r") as f:
print(f.read())
else:
print("File does not exist yet.")The with Statement — The Professional Way to Handle Files
Every example in Part 1 required a manual .close() call. Forget it once — because of an early return, an exception, or simply oversight — and the file stays locked until your program exits. The with statement eliminates this problem entirely:
with open("stored_files/program_data.txt", "w") as my_file:
my_file.write("Hello There! Program data stored successfully!\n")
my_file.write("Hello There! Program data stored successfully!\n")
my_file.write("Hello There! Program data stored successfully!\n")
# File is automatically closed here — no .close() neededThe with block is a context manager: it guarantees the file is closed the moment the indented block ends, even if an error is raised inside it. This is the standard pattern in all professional Python code.
🖼️ Visual Suggestion: Side-by-side comparison of the manual open/close pattern vs. the
withstatement, highlighting where.close()is called in each.
All Three Operations Using with
Writing:
with open("data.txt", "w") as f:
f.write("Fresh content.\n")Reading:
with open("data.txt", "r") as f:
content = f.read()
print(content)Appending:
with open("data.txt", "a") as f:
f.write("Appended safely.\n")Try converting the read and append examples from Part 1 to use the with statement — it's a good exercise to solidify the pattern.
Understanding the File Pointer
Every time you open a file, Python creates an internal cursor called the file pointer (also known as the file handle). It determines the exact position in the file where the next read or write operation will begin.
🖼️ Visual Suggestion: Diagram of a text file with a visible arrow representing the file pointer, showing its starting position in
"r","w", and"a"modes respectively.
How the Pointer Behaves by Mode
| Mode | Pointer starts at | Effect |
|---|---|---|
"r" | Beginning of file | Reads forward from the start |
"w" | Beginning of file | Erases file, writes from the start |
"a" | End of file | Writes after all existing content |
"r+" | Beginning of file | Reads and writes from the start |
"a+" | End of file | Can read, but pointer starts at end — reads return empty unless repositioned |
This is the deeper reason why write mode overwrites and append mode preserves: they start the pointer in different places. Write mode clears the file and places the pointer at position 0. Append mode places the pointer at the last byte of existing content and adds from there.
How the Pointer Moves During Read Operations
When you call .read(), the pointer moves all the way to the end of the file in one step. When you call .readline(), it moves to the start of the next line:
with open("data.txt", "r") as f:
print(f.readline()) # → Line 1 (pointer now at start of Line 2)
print(f.readline()) # → Line 2 (pointer now at start of Line 3)
print(f.tell()) # → current byte position of the pointer.tell() returns the current byte offset of the pointer — useful for debugging.
Manually Repositioning the Pointer with .seek()
You can move the pointer to any position using .seek():
with open("data.txt", "r") as f:
content = f.read() # reads entire file, pointer is now at the end
f.seek(0) # reset pointer to the beginning
first_line = f.readline() # reads Line 1 again
print(first_line)This also explains why "a+" mode can't read existing content without a manual seek: the pointer starts at the end, so a .read() call immediately finds nothing. You'd need f.seek(0) first to reposition it to the start.
with open("data.txt", "a+") as f:
f.seek(0) # move pointer to the beginning
print(f.read()) # now this works
f.write("\nNew line") # appending still works — write always goes to the end in 'a' modePutting It All Together — A Practical Example
Here's a script that demonstrates the complete file handling lifecycle: create, write, read, append, check existence, and delete:
import os
FILE_PATH = "stored_files/session_log.txt"
# 1. Create and write
with open(FILE_PATH, "w") as f:
f.write("Session started.\n")
f.write("User logged in.\n")
# 2. Read back what was written
with open(FILE_PATH, "r") as f:
print("--- Initial content ---")
print(f.read())
# 3. Append a new entry
with open(FILE_PATH, "a") as f:
f.write("User performed an action.\n")
# 4. Read again to confirm append
with open(FILE_PATH, "r") as f:
print("--- After append ---")
print(f.read())
# 5. Safely delete the file
if os.path.exists(FILE_PATH):
os.remove(FILE_PATH)
print("Log file deleted.")Output:
--- Initial content ---
Session started.
User logged in.
--- After append ---
Session started.
User logged in.
User performed an action.
Log file deleted.File Handling Cheat Sheet
import os
# Write (creates file, overwrites existing content)
with open("file.txt", "w") as f:
f.write("content\n")
# Append (adds to end, preserves existing content)
with open("file.txt", "a") as f:
f.write("more content\n")
# Read entire file
with open("file.txt", "r") as f:
data = f.read()
# Read line by line
with open("file.txt", "r") as f:
for line in f:
print(line.strip())
# Read all lines into a list
with open("file.txt", "r") as f:
lines = f.readlines()
# Check file exists before reading or deleting
if os.path.exists("file.txt"):
with open("file.txt", "r") as f:
print(f.read())
# Delete a file safely
if os.path.exists("file.txt"):
os.remove("file.txt")
# Get current pointer position
with open("file.txt", "r") as f:
f.read()
print(f.tell()) # byte position at end of file
# Reset pointer to beginning
with open("file.txt", "r") as f:
f.read()
f.seek(0)
print(f.readline()) # back to line 1What's Next?
Now that you can persist data reliably and handle files safely, the next step is learning what to do when things go wrong. The next tutorial covers error handling in Python — using try, except, and finally to write programs that fail gracefully instead of crashing unexpectedly.
Happy coding. 🐍