Python
Step 45. File Input/Output — Making Programs That Remember
Level 1 — Programming and the Computer’s Inner Workings | Difficulty ★★☆☆☆ | Estimated time: 3 hours
Prerequisites: Steps 41–44 complete. You can use variables, conditionals, loops, functions, and modules.
- What you need: the Step 41 workbench (Python + VSCode). Files created today appear in the same folder as the program.
- Caution: today’s exercise is 100% safe. However,
"w"mode starts by erasing everything in the existing file, so never open an important file other than practice files withw.
Memory (variables) is volatile. The moment a program shuts off, even the most diligently gathered data evaporates. So whatever you want to keep, you write to disk — to a file. File I/O looks like a dull basic, but it’s actually the most reused skill in this book. Saving scan results, reading and analyzing logs, loading challenge data — all start with today’s single line of open. The three formats you learn today (txt, CSV, JSON) are the basic forms of data exchange.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Create, write, and read files with
with open() - Explain the difference between modes
r/w/aand the danger ofw - Know why
encoding="utf-8"is needed - Read CSV with
splitand save/restore JSON withjson.dump/json.load - Use the safe pattern of checking existence with
os.path.existsbefore reading - Complete an accumulating notepad program where records pile up with every run
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python 3.12 or later, VSCode (the Step 41 workbench) |
| Today’s commands/grammar | open(path, mode, encoding="utf-8"), with ~ as f:, f.write()/f.read(), modes r/w/a, n and strip(), json.dump/json.load, os.path.exists, enumerate |
| Concepts needed | file modes, encoding (UTF-8), the three storage formats txt/CSV/JSON, relative paths |
2-1. Opening Files — Three Postures (Modes)
In Python, you open a file with open(). When opening, you must decide a mode.
"r"(read): read-only. Error if the file doesn’t exist."w"(write): write anew. Starts from a blank sheet after erasing all existing content."a"(append): add on. Continues writing after the existing content.
Of these, "w" is the most dangerous. Accidentally open an important file with w and its contents evaporate. If you meant to append, it must be "a".
2-2. The with Statement — Automating File Closing
A file you open must be closed. Leave it unclosed and saves can be lost or the file can lock. Humans are forgetful animals, so Python has syntax that closes files automatically.
with open("memo.txt", "w", encoding="utf-8") as f:
f.write("first linen")
The moment the with block (the indented region) ends, Python closes the file for you. This is modern Python’s standard posture, and this book always uses this style.
2-3. Encoding — The Code Table Turning Characters into Numbers
Computers store characters as numbers, and the code table used then is the encoding. Most garbled-text accidents happen because "the code table at writing" and "the code table at reading" differ. The world-standard code table is UTF-8, and we always specify encoding="utf-8". This one habit prevents countless garbled-text incidents in your future.
2-4. Three Formats — Storage Forms by Purpose
- Text (txt): just lines of text. Memos, logs. Easiest for humans to read.
- CSV (Comma-Separated Values): a table with cells split by commas. One
name,scoreline is one row, and it opens directly in Excel. - JSON (JavaScript Object Notation): a text format looking almost identical to Python’s dictionaries and lists. It’s the de facto standard for storing nested structures whole.
3. Follow Along
3-1. First File — Write and Check
Create a file file1.py.
with open("memo.txt", "w", encoding="utf-8") as f:
f.write("Today I learned Python file I/O.n")
f.write("Files remain after the program shuts off.n")
print("Saved!")
Saved!
(Verified on 2026-09-09. After running, memo.txt was actually created in the same folder as the program. Double-click it and check the contents.)
How to read it: n is the special character for "line break." write doesn’t kindly break lines for you, so if you need a line break, write it in yourself.
3-2. Reading — Loading What You Left Behind
file2.py:
with open("memo.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
Today I learned Python file I/O.
Files remain after the program shuts off.
(Verified on 2026-09-09. One extra blank line shows because of the final n.)
How to read it: .read() brings the whole file as one long string. The contents stay the same even after the program is shut off and restarted — that is the very reason files exist.
To process one line at a time, do this (output run right after, in the same verification):
with open("memo.txt", "r", encoding="utf-8") as f:
for line in f:
print("Line read:", line.strip())
Line read: Today I learned Python file I/O.
Line read: Files remain after the program shuts off.
(Verified on 2026-09-09.)
How to read it: looping a file with for pulls it out line by line. .strip() is the technique of peeling off the n at the end of each line. Print without it and lines look double-spaced — compare it yourself.
3-3. Appending — Diary Mode
file3.py:
memo = input("Today's memo: ")
with open("diary.txt", "a", encoding="utf-8") as f:
f.write(memo + "n")
print("Recorded.")
Run this program three times, entering different memos. In verification, Woke up early, Lunch was gimbap, Python is fun were entered in turn, and here are the resulting contents of diary.txt (verified on 2026-09-09):
Woke up early
Lunch was gimbap
Python is fun
How to read it: because it’s "a" mode, the existing content wasn’t erased and the new lines were appended. What if it had been "w"? As verified by testing — write twice to the same file with w and only the last one remains (verified on 2026-09-09: after writing "first run" and then "second run" with w, the file contained only second run).
Try it yourself: change the
"a"to"w"in 3-3’s code, run it three times, and check diary.txt. This experiment engraves today’s most important safety rule into your body.
3-4. CSV — Making a Table
file4.py:
with open("scores.csv", "w", encoding="utf-8") as f:
f.write("name,scoren")
f.write("Minsu,85n")
f.write("Jiyoung,92n")
f.write("Chris,78n")
print("CSV saved")
After running, open scores.csv in Notepad or Excel. The comma is the cell boundary. To read it, Step 42’s split works as-is (continuing with the same file):
with open("scores.csv", "r", encoding="utf-8") as f:
for line in f:
name, score = line.strip().split(",")
print(f"{name}: {score}")
CSV saved
name: score
Minsu: 85
Jiyoung: 92
Chris: 78
(Verified on 2026-09-09.)
How to read it: line.strip().split(",") means "peel the line’s end and split on commas." The result is two pieces, so two variables receive them. Look at the first line of the verified output, name: score — the header line got read as if it were data. Skipping the header line is solved in 3-8.
3-5. JSON — Storing a Structure Whole
file5.py:
import json
host = {"ip": "192.168.0.23", "ports": [22, 80], "os": "ubuntu"}
with open("host.json", "w", encoding="utf-8") as f:
json.dump(host, f, ensure_ascii=False)
with open("host.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded)
print(loaded["ports"][1])
{'ip': '192.168.0.23', 'ports': [22, 80], 'os': 'ubuntu'}
80
(Verified on 2026-09-09.)
How to read it: json.dump(basket, file) converts the basket to JSON text and saves it; json.load(file) reads it and restores it back into a real dictionary. Since it’s restored, you can pull from it right away like loaded["ports"][1]. ensure_ascii=False is the option "save non-ASCII characters as-is, unbroken." CSV only handles flat tables, but JSON stores nested baskets (a list inside a dictionary) whole.
3-6. When the File Doesn’t Exist — Check Before Reading
Opening a nonexistent file with r produces this error (verified on 2026-09-09):
Traceback (most recent call last):
File "<string>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'no_such_file.txt'
So asking before opening is the seatbelt. file6.py:
import os
filename = "diary.txt"
if os.path.exists(filename):
with open(filename, "r", encoding="utf-8") as f:
print(f.read())
else:
print("No diary yet. Start with your first memo.")
How to read it: os.path.exists(path) is the true/false question "does that file exist?", and os is the basic module for talking with the operating system. In verification, the diary.txt made in 3-3 existed, so its contents were printed (verified on 2026-09-09). For reference, os.getcwd() tells you "the folder the program is standing in right now" — used at Wall 3.
3-7. enumerate — Auto-Numbering Lines
When reading a file line by line, you often need numbers. Don’t count by hand — use the tool.
with open("diary.txt", "r", encoding="utf-8") as f:
for num, line in enumerate(f, start=1):
print(f"Record {num}: {line.strip()}")
Record 1: Woke up early
Record 2: Lunch was gimbap
Record 3: Python is fun
(Verified on 2026-09-09.)
How to read it: enumerate(basket, start=1) attaches a number tag with every item pulled. Without start, it counts from 0. "On which line is the problem?" is a regular question in log analysis, and enumerate gives the answer.
3-8. Format Conversion Practice — CSV to JSON
Let’s build a converter moving between the two formats. Today’s full-body exercise. convert.py:
import json
students = []
with open("scores.csv", "r", encoding="utf-8") as f:
first = True
for line in f:
if first: # skip the first line (the header)
first = False
continue
name, score = line.strip().split(",")
students.append({"name": name, "score": int(score)})
with open("scores.json", "w", encoding="utf-8") as f:
json.dump(students, f, ensure_ascii=False, indent=2)
print("Conversion complete! Open scores.json.")
Conversion complete! Open scores.json.
The actual contents of the generated scores.json (verified on 2026-09-09):
[
{
"name": "Minsu",
"score": 85
},
{
"name": "Jiyoung",
"score": 92
},
{
"name": "Chris",
"score": 78
}
]
How to read it: it reads the CSV line by line, builds dictionaries, gathers them into a list, and saves as JSON. The first line is the header, so the first flag skips it — the fix for the name: score output problem in 3-4. indent=2 is the option that indents JSON for human readability. A converter that "reads → gathers into baskets → saves in another format" is one of the most common scripts in real work.
4. Missions & Exercises
Mission — The Accumulating Notepad
Create notepad.py and assemble the following requirements.
- On run, first show past memos with numbers. (If the file doesn’t exist, print
Start your first memo.) - Receive a new memo as input and append it to the file.
- Automatically attach a date in front of the memo. Hint:
import datetime
today = datetime.date.today().isoformat() # form like "2026-09-09"
- The storage format is
date | memoon one line. - Run the program at least three times and confirm the accumulation.
Advanced (optional): make a menu with "1. View 2. Write 3. Delete all," and for option 3, ask once more whether to really delete. Deletion is writing an empty string with "w".
Exercises
Question 1. Explain the difference between modes r, w, and a in one sentence each, and answer which of the three carries the risk of losing data.
Question 2. What can happen if you leave out encoding="utf-8", and why must you attach it both when writing and when reading?
Question 3. If you run f.write("first line") and f.write("second line") in sequence, what do the file contents look like? If the shape you want is two lines, how should you fix it?
Question 4. Explain the role difference between CSV and JSON, and answer — with reasons — which fits data like "a list of servers, where each server also has a list of open ports."
Answers & completion criteria · expand/collapse
5. Model Answers & Completion Criteria
Mission Model Answer
import os
import datetime
filename = "notes.txt"
# 1. Show past memos
if os.path.exists(filename):
print("=== Past memos ===")
with open(filename, "r", encoding="utf-8") as f:
for num, line in enumerate(f, start=1):
print(f"{num}. {line.strip()}")
else:
print("Start your first memo")
# 2-4. Append a new memo
memo = input("New memo: ")
today = datetime.date.today().isoformat()
with open(filename, "a", encoding="utf-8") as f:
f.write(f"{today} | {memo}n")
print("Recorded.")
Example run (after three runs):
=== Past memos ===
1. 2026-09-09 | Reviewed Python file I/O
2. 2026-09-09 | The with statement closes files for me
New memo: enumerate attaches numbers
Recorded.
Commentary: ① os.path.exists builds the fork of "if missing, guide; if present, print." ② Separating the reading (r) block and the writing (a) block is important — try to read right after writing inside the same with and you get an empty result (Wall 4). ③ enumerate(f, start=1) attaches the numbers, and f"{today} | {memo}n" builds the storage format.
How to verify: ① See whether "Start your first memo" appears on the first run (delete notes.txt to test). ② Run three times and see whether numbers accumulate as 1, 2, 3. ③ Open notes.txt in Notepad and confirm the date | memo format.
Exercise Answers
Answer 1. r is read-only (error if missing), w erases all existing content and writes anew, a appends after the existing content. The dangerous one is w — in verification too, writing twice with w made the first content disappear, leaving only the last (verified on 2026-09-09). To append, it must be a.
Answer 2. Without specifying an encoding, the operating system’s default code table is used, and if that clashes with the file’s code table, characters get garbled. Since characters restore correctly only when the writing and reading code tables match, fixing both to encoding="utf-8" is safe.
Answer 3. They concatenate without a line break into first linesecond line. That’s because write doesn’t break lines automatically. You must insert n yourself, like f.write("first linen"), to get two lines.
Answer 4. CSV is "a flat table split by commas," fitting data with fixed rows and columns, while JSON stores dictionary/list structures whole, fitting nested data. "A port list per server" is a nested structure — a list inside each server — so JSON fits; the 3-8 verification confirmed that a list of dictionaries saved to JSON as-is.
Completion Criteria Checklist
- [ ] I can write and read files with
with open() - [ ] I confirmed by experiment the difference between the three modes
r/w/aand the danger ofw - [ ] I can explain why
encoding="utf-8"is needed - [ ] I know the roles of
nandstrip() - [ ] I can read CSV with
splitand restore JSON withjson.load - [ ] I can use the
os.path.existssafe pattern andenumeratenumbering - [ ] I completed the mission (accumulating notepad) and confirmed accumulation over three runs
6. Common Pitfalls & Fixes
Wall 1. Characters come out garbled
Symptom: characters come out as garbled symbols when saving or reading.
Cause: you left out encoding="utf-8". Without it, the operating system’s default code table is used, and if that table clashes with the file’s, characters break.
Fix: make attaching encoding="utf-8" to every open a finger habit. You must attach it both when writing and when reading.
Wall 2. Opened with "w" and everything got erased
Symptom: you meant to append, but the old content evaporated.
Cause: w unconditionally starts from a blank sheet (verified on 2026-09-09: writing twice to the same file with w left only the last content). Appending is a.
Fix: build the habit of asking yourself before writing — "erase and write fresh? (w) or continue? (a)". For important files, make a copy before working.
Wall 3. I get a FileNotFoundError
Symptom (verified on 2026-09-09):
FileNotFoundError: [Errno 2] No such file or directory: 'no_such_file.txt'
Cause: the file truly doesn’t exist, or the program is running from a different folder. A relative path ("memo.txt") is based on "the folder the program is currently running in."
Fix: check the folder you’re standing in with import os; print(os.getcwd()) (in the verification environment, the path of the folder containing the script printed). And use 3-6’s exists pattern to build a "read if present, guide if missing" structure.
Wall 4. I read, but only an empty string comes out
Symptom: the read() result is empty.
Cause: you tried to read right after writing inside the same with, or you opened the file with w instead of r and just erased it.
Fix: split writing and reading into separate with blocks. Only when the writing block ends (the file closes) is the save complete; open the reading block after that. The mission model answer is exactly this structure.
7. Summary
Today’s Concepts
| Concept | One-line description |
|---|---|
| File mode | r (read) / w (erase and write) / a (append) |
| with statement | The standard posture that auto-closes the file when the block ends |
| Encoding | The code table turning characters into numbers — always specify UTF-8 |
| CSV | A flat table format with cells split by commas |
| JSON | A format storing dictionary/list structures whole |
| Relative path | A path based on "the folder the program is running in" |
Today’s Grammar
| Grammar | What it does |
|---|---|
with open(path, mode, encoding="utf-8") as f: |
Open a file and auto-close it |
f.write(string) / f.read() |
Write / read everything |
for line in f: |
Loop a file line by line |
n / strip() |
Insert a line break / peel a line’s end |
json.dump(basket, f) / json.load(f) |
Save a basket as JSON / restore it |
os.path.exists(path) |
Ask whether a file exists |
enumerate(basket, start=1) |
Pull one by one with number tags attached |
Instincts More Important Than Grammar
Today your programs gained memory. A program that doesn’t forget when turned off. Saving is recording, and recording is responsibility. As you start building tools, you’ll leave artifacts like scan results and analysis logs as files, and those files are also a record of "what I did." ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Artifacts from your own lab become a portfolio; artifacts from unauthorized targets become evidence.
One last thing. JSON is the internet’s common language. When you learn web requests later, most responses servers return are JSON, and that one line of json.load you used today to restore a dictionary works exactly the same that day. Only the source differs — reading from a file versus receiving from the network.
Once every box is checked, Step 45 is complete.
ONE STEP FURTHER
Finished this lesson?
Check the completion criteria, then mark your progress.