Step 45. File Input/Output — Making Programs That Remember

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 with w.

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/a and the danger of w
  • Know why encoding="utf-8" is needed
  • Read CSV with split and save/restore JSON with json.dump/json.load
  • Use the safe pattern of checking existence with os.path.exists before 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 line\n")

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,score line 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,score\n")
    f.write("Minsu,85\n")
    f.write("Jiyoung,92\n")
    f.write("Chris,78\n")
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.

  1. On run, first show past memos with numbers. (If the file doesn’t exist, print Start your first memo.)
  2. Receive a new memo as input and append it to the file.
  3. Automatically attach a date in front of the memo. Hint:
import datetime
today = datetime.date.today().isoformat()   # form like "2026-09-09"
  1. The storage format is date | memo on one line.
  2. 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."


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 line\n"), 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/a and the danger of w
  • [ ] I can explain why encoding="utf-8" is needed
  • [ ] I know the roles of \n and strip()
  • [ ] I can read CSV with split and restore JSON with json.load
  • [ ] I can use the os.path.exists safe pattern and enumerate numbering
  • [ ] 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. Click the checkbox in the sidebar to save your progress.