Opening a file in append mode ('a' or 'a+') positions the write pointer strictly at the end of the file (EOF), preserving all existing contents while allowing new data to be seamlessly attached.
Non-Destructive Guarantee: Unlike write mode ('w'), which immediately truncates and erases existing files, append mode creates the file if it does not exist, or attaches new data to the end if it already contains records.
When you pass 'a' as the mode argument to open(), the operating system ensures two essential behaviors:
write() operation places new bytes directly after the last byte of the file.# Open in append mode ('a') with UTF-8 encoding
with open("application.log", "a", encoding="utf-8") as log_file:
log_file.write("2026-10-14 10:48:00 - Service initialized successfully.\n")
log_file.write("2026-10-14 10:48:05 - Listening on port 8080.\n")
print("Entries successfully appended without modifying previous log entries.")
Python distinguishes between write-only append ('a') and read-and-append ('a+'):
'a' (Append Only): You can only write to the file. Attempting to call read() or readline() raises an io.UnsupportedOperation: not readable error.'a+' (Append & Read): Both writing and reading are permitted. However, because the file pointer begins at the end of the file, calling read() immediately returns an empty string (""). You must call seek(0) to read from the beginning.with open("journal.txt", "a+", encoding="utf-8") as fob:
# 1. Append a new record
fob.write("Record 3: Transaction verified.\n")
# 2. Reading immediately here returns empty string because pointer is at EOF
print("Immediate read at EOF:", repr(fob.read())) # ''
# 3. Rewind to beginning to read all cumulative contents
fob.seek(0)
entire_history = fob.read()
print("Cumulative history:\n" + entire_history)
Compare the read/write capabilities and cursor placements across Python file access modes:
| Mode | Readable? | Writable? | Initial Cursor | Truncates File? | Creates If Missing? |
|---|---|---|---|---|---|
'r' |
Yes | No | Start (0) | No | No (raises error) |
'w' |
No | Yes | Start (0) | Yes (erases data) | Yes |
'a' |
No | Yes | End (EOF) | No (preserves data) | Yes |
'r+' |
Yes | Yes | Start (0) | No | No (raises error) |
'w+' |
Yes | Yes | Start (0) | Yes (erases data) | Yes |
'a+' |
Yes | Yes | End (EOF) | No (preserves data) | Yes |
A frequent trap for Python engineers is assuming that calling fob.seek(0) in append mode allows overwriting the beginning of the file. Under POSIX and standard C runtime semantics (the O_APPEND flag), all write operations are forcibly relocated to EOF immediately before writing, regardless of the current seek position:
with open("notes.txt", "a", encoding="utf-8") as f:
# Explicitly attempt to move file cursor to offset 0
f.seek(0)
# Write still appends to the END of the file!
f.write("Appended at EOF despite seek(0) call.\n")
If you genuinely require in-place updating at arbitrary byte offsets without erasing existing content, use mode 'r+' instead of 'a'.
Construct a lightweight, production-grade audit logger that formats and writes events with ISO 8601 timestamps:
import datetime
def log_event(filepath: str, level: str, message: str) -> None:
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
log_entry = f"[{timestamp}] [{level.upper()}] {message}\n"
with open(filepath, "a", encoding="utf-8") as stream:
stream.write(log_entry)
log_event("service.log", "info", "Background worker thread started")
log_event("service.log", "warning", "Cache miss for key user:4092")
When appending rows to a CSV dataset, check if the file is brand new to conditionally write the header row only once:
import csv
import os
def append_student_record(csv_path: str, record: list) -> None:
file_exists = os.path.exists(csv_path) and os.path.getsize(csv_path) > 0
with open(csv_path, "a", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(csvfile)
if not file_exists:
writer.writerow(["ID", "Name", "Math", "Science", "English", "Total"])
writer.writerow(record)
append_student_record("students.csv", [1, "Ravi", 20, 30, 40, 120])
append_student_record("students.csv", [2, "Raju", 30, 40, 50, 130])
If the existing file does not end with a newline character (
), your first appended string will attach directly to the end of the existing final line (e.g. "OldTextNewText"). Always ensure appended strings begin or end with
.
Attempting to read from a file opened strictly in mode 'a' raises io.UnsupportedOperation: not readable. Use mode 'a+' if you need to inspect previous contents before appending.
Execute this self-contained test harness verifying append isolation, mode 'a+' rewind, and append invariants:
import tempfile
import os
import io
class AppendVerifier:
@staticmethod
def append_line(path: str, text: str) -> int:
with open(path, "a", encoding="utf-8") as stream:
return stream.write(text + "\n")
@staticmethod
def read_all_lines(path: str) -> list[str]:
with open(path, "r", encoding="utf-8") as stream:
return [line.rstrip("\n") for line in stream]
# Verification unit tests
with tempfile.TemporaryDirectory() as td:
v = AppendVerifier()
test_file = os.path.join(td, "verify.log")
# Test 1: Append to non-existent file creates it
v.append_line(test_file, "Initial line")
assert v.read_all_lines(test_file) == ["Initial line"]
# Test 2: Append retains previous content
v.append_line(test_file, "Second line")
assert v.read_all_lines(test_file) == ["Initial line", "Second line"]
# Test 3: Mode 'a' raises UnsupportedOperation on read()
with open(test_file, "a", encoding="utf-8") as f:
try:
f.read()
assert False, "Should fail with not readable"
except io.UnsupportedOperation:
pass
# Test 4: Mode 'a+' allows read after seek(0)
with open(test_file, "a+", encoding="utf-8") as f:
f.write("Third line\n")
assert f.read() == ""
f.seek(0)
assert "Third line" in f.read()
print("All file append unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all file append exercises interactively, contrast 'a' with 'a+' and 'w' modes, implement structured CSV and event logging pipelines, and verify EOF invariants in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.