The command says “completed successfully.” The file is now empty.
You are working in a campus lab, at home on a Windows laptop, or on a small portfolio script after a shift. You open a report, make one change, and save it. Later, someone asks for the original rows. The program has no backup, the output path was different from the one you expected, and the mode was "w".

Reading and writing files in Python is not just about remembering open(). It is about deciding what can be replaced, what must be preserved, where the data lives, which encoding describes it, and how another person can reproduce the operation.
Before you write: name the file you are willing to change
Start with a harmless text file:
from pathlib import Path
source = Path("data") / "tickets.txt"
print(source)
print(source.exists())Python’s pathlib documentation describes Path objects as a cross-platform way to represent filesystem paths. The important habit is not that pathlib makes every operation safe. It makes the path an explicit object that you can inspect, compose, and pass to the operation.
On one school computer, the current directory may be your project folder. On another computer, a terminal may start in your home directory. If data/tickets.txt works in one place and fails in another, print the current directory before changing the code:
from pathlib import Path
print(Path.cwd())
print(Path("data").resolve())
print(Path("data/tickets.txt").exists())This is a useful distinction for an American beginner moving between a community-college lab, a library workstation, and a personal laptop: the code did not necessarily change, but the environment around the relative path did.
Reading is an operation with a contract
For a small text file, use a context manager so Python closes the file when the block ends:
from pathlib import Path
message_path = Path("message.txt")
with message_path.open("r", encoding="utf-8") as file:
message = file.read()
print(message)The official Python tutorial on reading and writing files documents the basic modes and the role of the with statement. The explicit encoding="utf-8" tells Python how to decode the bytes into text. If a file comes from another system and contains a different encoding, blindly adding errors="ignore" may hide data loss. Preserve the decoding error while you identify the source format.
Do not read an image, ZIP archive, PDF, or other binary file as if it were ordinary text:
with open("logo.png", "rb") as source:
image_bytes = source.read()
with open("logo-copy.png", "wb") as destination:
destination.write(image_bytes)Text mode decodes characters and may translate line endings. Binary mode gives you bytes. The Python I/O documentation explains the distinction between text, buffered binary, and raw I/O. A beginner does not need to memorize every stream class, but should know that adding an encoding to binary mode is a category mistake.
The one-character mode that can erase your work
These two snippets look almost identical:
with open("notes.txt", "w", encoding="utf-8") as file:
file.write("new version\n")with open("notes.txt", "a", encoding="utf-8") as file:
file.write("another line\n")"w" opens for writing and can truncate an existing file before the new content is complete. "a" opens for appending and writes at the end. "x" creates a new file and fails if the path already exists. "r" reads without requesting a write.
rRead an existing file. Missing path: failure.
wReplace the file’s contents. Existing data can be lost.
aAdd at the end. Repeated runs can duplicate output.
xCreate only if the destination does not exist.
For a classroom exercise, "w" may be exactly what you want for a disposable output file. For a customer report or a settings file, it may be the wrong decision. The mode is part of the data contract, not a punctuation detail.
Case file: the CSV changed shape while you were not looking
Imagine a beginner receives a CSV export of service tickets. One version has a header row; another was saved from a spreadsheet without the header. A script that assumes one shape can silently produce incorrect columns.
import csv
from pathlib import Path
input_path = Path("data/tickets.csv")
with input_path.open("r", newline="", encoding="utf-8") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["ticket_id"], row["status"])The official CSV module documentation explains why CSV parsing should use the module rather than splitting each line on commas. Quoted commas, line endings, and dialect differences make “just call split(',')” a fragile shortcut.
Make the expected columns visible before transforming anything:
required = {"ticket_id", "status"}
with input_path.open("r", newline="", encoding="utf-8") as file:
reader = csv.DictReader(file)
actual = set(reader.fieldnames or [])
missing = required - actual
if missing:
raise ValueError(f"Missing required columns: {sorted(missing)}")That check changes the failure from a mysterious later error into a specific data-contract message. In a support or QA workflow, it also creates a useful record: the input file did not satisfy the expected schema. The BLS description of computer support specialists includes diagnosing customer problems and documenting the problem description. File handling is part of that diagnostic work when a report depends on an input export.
Write a new file before replacing the old one
For a beginner project, write a new output file first:
output_path = Path("data/tickets-clean.csv")
with output_path.open("w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=["ticket_id", "status"])
writer.writeheader()
writer.writerows([
{"ticket_id": "1042", "status": "open"},
{"ticket_id": "1043", "status": "closed"},
])The original remains available for comparison. That matters in a class assignment, a portfolio demonstration, and a QA check because you can show input, transformation, and output separately.
When a destination must be replaced but should not be left half-written, a temporary file in the same directory is a stronger pattern:
import os
import tempfile
from pathlib import Path
path = Path("settings.json")
content = '{"ready": true}\n'
temporary_name = None
try:
with tempfile.NamedTemporaryFile(
"w",
encoding="utf-8",
dir=path.parent,
delete=False,
) as temporary:
temporary.write(content)
temporary_name = temporary.name
os.replace(temporary_name, path)
finally:
if temporary_name:
temporary_path = Path(temporary_name)
if temporary_path.exists():
temporary_path.unlink()Python’s tempfile documentation explains the temporary-file facility. Writing in the same directory helps the replacement occur on the same filesystem. This does not solve concurrent writes, permissions, backups, or every durability guarantee; it simply narrows the window in which a failed write can destroy the destination.
JSON is data, not a string you assemble by hand
For nested settings or structured records, use the JSON module:
import json
from pathlib import Path
settings_path = Path("settings.json")
settings = {"theme": "dark", "retries": 3}
with settings_path.open("w", encoding="utf-8") as file:
json.dump(settings, file, indent=2)
with settings_path.open("r", encoding="utf-8") as file:
loaded = json.load(file)
if not isinstance(loaded.get("retries"), int):
raise ValueError("retries must be an integer")The Python JSON documentation describes serialization and deserialization for common data hierarchies. JSON does not preserve every Python object, tuple identity, class instance, or function. Validate the loaded shape before using it, especially when the file can be edited by a person or produced by another program.
Never put passwords, API keys, session tokens, or private customer information into a learning file just because the format is convenient. A file can be local and still be uploaded, committed to Git, copied into a support ticket, or included in a backup.
Same script, different computer, different evidence
When a file operation fails on a classmate’s machine, record the environment before guessing:
import platform
from pathlib import Path
print("Python:", platform.python_version())
print("System:", platform.system())
print("Working directory:", Path.cwd())
print("Input exists:", Path("data/tickets.csv").exists())A Windows path may be written with backslashes, while a macOS or Linux path uses forward slashes. Prefer path composition through Path instead of manually concatenating strings. If the project depends on a specific current directory, document the command and folder rather than assuming every learner will launch it from the same place.
This is the kind of detail a beginner can show in a portfolio without claiming professional experience: “The script failed because the relative path was evaluated from a different working directory. I printed the environment, changed the project-relative path, and added a check for the input file.” That is evidence of a debugging decision, not a promise that the project works everywhere.
What should be reversible?
Use this decision before writing:
Disposable output: write a new file with a clear name.
Append-only log: use "a", but plan for duplicates and rotation.
Important existing file: make a backup or write a temporary replacement.
Unknown input: open read-only, inspect the shape, and validate before transforming.
Shared or concurrent data: do not assume a local file is a database; define ownership or use a storage system designed for concurrent access.
The right answer depends on the consequence of losing or corrupting the file. A student’s generated practice report and a customer’s source export should not receive the same write strategy.
One file operation is not a complete test
Before calling a script finished, test the states that change the decision:
def describe_input(path):
if not path.exists():
return "missing"
if not path.is_file():
return "not a regular file"
return "ready"
assert describe_input(Path("does-not-exist.txt")) == "missing"
assert describe_input(Path("data/tickets.csv")) in {"ready", "missing"}Then test a temporary directory instead of writing into your project’s real data:
from tempfile import TemporaryDirectory
from pathlib import Path
with TemporaryDirectory() as folder:
path = Path(folder) / "result.txt"
path.write_text("first\n", encoding="utf-8")
assert path.read_text(encoding="utf-8") == "first\n"The pytest getting-started guide shows how to turn expected behavior into repeatable tests. A test that writes to a temporary location is easier to run again than one that overwrites the only copy of a file on your laptop.
Where this appears in beginner work
A Python student may begin by saving a cleaned CSV for a class exercise. A support trainee may need to preserve the customer’s original export while producing a diagnostic copy. A QA learner may compare an expected JSON fixture with the file a script generated. A junior developer may need to explain why a configuration file is replaced only after validation passes.
The Bureau of Labor Statistics describes software developers and QA analysts as people who develop, test, identify, and document software behavior. The O*NET QA profile includes documenting defects, executing tests, and retesting fixes. File operations are not a job title by themselves, but they are part of the evidence chain behind many tasks: what entered the program, what changed, what was saved, and how someone can verify it.
A current market note, without a career promise
Recent U.S. labor-market signals point toward a broader expectation than “know one language.” Employers and occupational descriptions continue to connect software work with testing, documentation, troubleshooting, data handling, and communication. At the same time, reports such as NACE’s 2026 Job Outlook Spring Update discuss growing expectations around AI skills in early-career work. That does not mean a beginner should skip file fundamentals and jump straight to automation tools.
A more defensible takeaway is to build small projects that show a complete chain: safe input, explicit transformation, preserved original, validated output, and a short explanation of failure modes. That evidence can support a class discussion, a portfolio review, or an entry-level conversation without pretending to be a job guarantee.
Keep the original until you can explain the replacement
For your next exercise, create a data/ folder with a small CSV and produce data/cleaned.csv without changing the source. Print the resolved paths, validate the required columns, write with an explicit encoding, and compare the number of rows before and after.
Then ask yourself five questions:
- Which path did the program actually use?
- What would happen if the file were missing?
- Which mode could erase existing data?
- How would I restore the original if the transformation were wrong?
- What evidence proves that the output is complete?
Reading and writing files becomes reliable when the code preserves your ability to inspect, compare, and undo. The syntax opens the file. The surrounding decisions protect the data.

Alex Carter is the editorial name behind Vandutz Academy, a programming blog for beginners. Alex reviews and tests the examples and explanations published on the site, with a focus on making Python, JavaScript, web development, and developer tools easier to understand.