Volatile Memory vs. Non-Volatile Storage
Variables and collections live inside RAM while your script runs. The moment execution halts, all working state is reclaimed by the OS. Real software must remember state across sessions—which necessitates Data Persistence, and the most universal mechanism for persistence is the File.
01-02 What is Data Persistence & What is a File?
In computer science, Persistence refers to the characteristic of data that survives the execution lifetime of the process that created it.
Blazing fast nanosecond access, but completely lost when power drops or the process terminates.
When you run the script tomorrow, it reads the saved file and seamlessly resumes state.
What is a File, Exactly?
Simply put, a file is a named collection of bytes stored on physical non-volatile media, referenced by an operating system file system. Every file possesses a name, an extension (.txt, .csv, .json), a path, byte dimensions, and operating system permission flags.
03-06
File Opening, Reading & The Safe with open() Pattern
Python interacts with files through the fundamental built-in open() function:
Why is `close()` Mandatory?
Operating systems maintain a finite ceiling of concurrent file handles. Unclosed files trigger file descriptor leaks, prevent OS write buffers from flushing to magnetic/flash platters, and keep files locked from external processes.
The Pythonic Standard: Context Managers (with open())
If an exception crashes your script between open() and close(), the file remains locked in limbo. Python solves this via Context Managers (with blocks), ensuring deterministic closure even under fatal exceptions:
07-11 File Modes (`r`, `w`, `a`, `x`) & Newline (`\n`) Caveats
How the OS prepares a file is controlled by the mode argument passed to open():
| Mode | Purpose | If File Does Not Exist | If File Already Exists |
|---|---|---|---|
| "r" | Read | Raises FileNotFoundError | Starts reading from byte 0 |
| "w" | Write | Creates empty file | Truncates immediately to 0 bytes! (Overwrites) |
| "a" | Append | Creates empty file | Positions pointer at end; preserves prior bytes |
| "x" | Exclusive Creation | Creates new file | Raises FileExistsError (guards against accidental wipe) |
Caution: The Destructive Truncation of `"w"` Mode
Opening an existing file in "w" mode will instantly obliterate every existing byte without prompting for confirmation. When your intention is to add records or write telemetry to existing logs, always use "a" (append).
Explicit Newlines (\n)
Unlike print(), which automatically appends a newline character, file.write() writes verbatim text. You must explicitly supply \n to create line breaks:
12-15 Multiple Ways to Read Files: Memory Ingestion vs Streaming
Python provides four primary access patterns depending on file magnitude and memory constraints:
file.read() — Ingesting Entire Content into One StringLoads all bytes into RAM at once. Excellent for config files and small documents.
content = file.read() # Whole file as a single str
chunk = file.read(100) # Reads precisely 100 characters
for line in file: — Memory-Efficient Line Streaming (Best Practice)Even if your log file is 20 Gigabytes, this never triggers an Out-Of-Memory (OOM) error! Python streams exactly one line into memory per iteration.
with open("huge_access.log", "r", encoding="utf-8") as file:
for line in file:
print(line.strip()) # Discards previous line from RAM as next is fetched
file.readline() — Reading a Single Individual LinePulls characters up to the next \n and advances the file pointer.
header = file.readline() # Consumes top line (e.g. CSV headers)
row1 = file.readline() # Consumes next row
file.readlines() — Parsing All Lines into a Python ListReturns a list of strings, retaining newline markers.
lines = file.readlines()
# Result: ["Apple\n", "Banana\n", "Mango\n"]
Bridging File Streams to String Processing
Applying the string methods learned in Lesson 11 directly transforms raw disk data into structured program structures:
16-18 Persisting User State & File Paths (Relative vs. Absolute)
Saving user interactions to disk allows our software to recognize returning users across application sessions:
Relative Paths vs. Absolute Paths
Specifying where a file lives on the storage tree follows two distinct philosophies:
Computed relative to the Current Working Directory (CWD) where Python is executed. Highly portable across different machines:
data/users.txt
The full canonical address starting from the filesystem root. Breaks easily if deployed to another user's machine or server:
C:\Projects\app\data\users.txt
19-21 UTF-8 Encoding, Text vs. Binary Mode & The Byte Pipeline
At the hardware layer, storage drives only record binary pulses (0s and 1s). Always pass explicit encoding="utf-8" to guarantee consistent rendering across platforms:
Mental Model: From Program String to Silicon Memory Cell
"Data"
b'Data'
NAND Flash Voltage
Text Mode vs. Binary Mode (`rb`, `wb`)
Binary assets—such as images (.png, .jpg), audio (.mp3), compressed archives (.zip), or executables—do not represent printable characters. Opening them with "rb" or "wb" operates on raw bytes:
22-27 Structured Data Persistence: CSV vs. JSON
Real software requires structured tabular or hierarchical data formats rather than freeform text blobs:
Standard format for spreadsheets, relational exports, and matrix datasets. Python's built-in csv module parses each line into a clean list of cells:
Name,City,Age
Salim,Dhaka,25
Rahim,Gazipur,30
import csv
with open("users.csv", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row) # ['Salim', 'Dhaka', '25']
The universal lingua franca of web APIs, document databases, and application configuration. Maps directly to Python dictionaries and lists:
import json
profile = {"name": "Salim", "age": 25}
with open("user.json", "w") as f:
json.dump(profile, f, indent=2)
import json
with open("user.json", "r") as f:
data = json.load(f)
print(data["name"]) # "Salim"
Architectural Comparison: CSV vs. JSON
| Dimension | CSV (Comma-Separated) | JSON (JavaScript Object Notation) |
|---|---|---|
| Ideal Domain | Flat tabular matrices, financial spreadsheets | Complex nested structures, web APIs, config trees |
| Hierarchy / Nesting | Poor / None (strictly 2D rows & columns) | Native and arbitrary depth |
| Standard Modules | csv.reader() / csv.writer() | json.dump() / json.load() |
28-32 Error Caveats, OS Permissions & Files vs. Databases
A cardinal rule of systems programming: External physical resources can never be assumed to exist or succeed without fail.
Users rename files, network shares disconnect, or paths misalign, raising instant FileNotFoundError crashes.
Directories may be read-only, another process might hold an exclusive lock, or admin rights may be required, triggering PermissionError.
Are Files Databases?
No! While flat files work wonderfully for small local stores and configs, large-scale software requires dedicated Database Management Systems (DBMS):
- Concurrency: Two processes writing simultaneously to a text file corrupt each other's stream. Databases support transactional concurrency locks.
- Indexing: Locating a user among 10 million rows in a text file demands scanning the whole file linearly (
O(n)time). Databases use B-Trees to resolve queries in microseconds (O(log n)). - Atomicity & Durability (ACID): Financial ledgers cannot risk half-written files if power fails mid-write. Databases guarantee all-or-nothing transactions.
33-36 Real-World Telemetry Logging & A Complete Persistent Notes App
Production servers record append-only operational events to log files (app.log) to audit transactions and investigate incidents:
A Complete Persistent Notes Application
A complete script showing state persistence: receiving user input, appending it to disk, and reloading historical entries:
37-42 RAM vs. Storage, Functional Modularity & The Complete Data Flow
Keep file operations cleanly modularized within reusable functions rather than scattering open() calls across your business logic:
Complete Software Data Flow Architecture
43 8 Hands-On Practice Challenges
Applied Coding LabTrue mastery comes through active synthesis. Implement each solution independently, then expand the panel to review reference implementations:
1 User Name Saver & Greeter
Task: Request a name via input(), write it to name.txt, then open the file in read mode and print the saved greeting.
name = input("Enter your name: ")
with open("name.txt", "w", encoding="utf-8") as f:
f.write(name)
with open("name.txt", "r", encoding="utf-8") as f:
print(f"Loaded from disk: {f.read()}")
2 Multi-Line Journal / Notes Recorder
Task: Append new notes to an existing journal file on separate lines using append mode ("a").
note = input("Write note: ")
with open("journal.txt", "a", encoding="utf-8") as f:
f.write(note + "\n")
print("Note appended successfully!")
3 Line-By-Line Stream Reader
Task: Iterate through a text file with for line in file: and print numbered, trimmed lines.
with open("names.txt", "r", encoding="utf-8") as f:
for idx, line in enumerate(f, start=1):
print(f"Entry {idx}: {line.strip()}")
4 File Statistics Analyzer (Lines, Words, Chars)
Task: Calculate total character count, total word count, and total lines within a text file.
with open("manuscript.txt", "r", encoding="utf-8") as f:
text = f.read()
lines = text.splitlines()
words = text.split()
chars = len(text)
print(f"Lines: {len(lines)}, Words: {len(words)}, Characters: {chars}")
5 In-File Keyword Searcher
Task: Search a document case-insensitively for a target word and report the matching line numbers.
target = input("Search query: ").lower()
with open("document.txt", "r", encoding="utf-8") as f:
for line_no, line in enumerate(f, start=1):
if target in line.lower():
print(f"Line {line_no}: {line.strip()}")
6 CSV Student Grade Ingestion
Task: Ingest student scores from a CSV table using the built-in csv module.
import csv
with open("students.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
for row in reader:
print(f"Student: {row[0]}, Score: {row[1]}, Grade: {row[2]}")
7 JSON Configuration Serializer & Loader
Task: Serialize a developer profile dictionary to disk with json.dump() and reload it with json.load().
import json
developer = {"name": "Salim", "role": "Engineer", "skills": ["Python", "SQL"]}
with open("dev.json", "w", encoding="utf-8") as f:
json.dump(developer, f, indent=2)
with open("dev.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(f"Skills: {', '.join(loaded['skills'])}")
8 Persistent Execution Counter
Task: Keep an execution tally saved in counter.txt so each invocation increments and prints the total lifetime run count.
count = 0
try:
with open("counter.txt", "r") as f:
count = int(f.read().strip())
except FileNotFoundError:
count = 0
count += 1
with open("counter.txt", "w") as f:
f.write(str(count))
print(f"Program has executed {count} time(s) across reboots!")
🧠 Lesson 12 Key Takeaways
- Data Persistence: RAM is strictly ephemeral working memory. Non-volatile disk writes are mandatory to retain state across process exits and reboots.
-
The `with open(...)` Context Manager: Never rely on manual
close()calls. Context managers guarantee deterministic handle cleanup even under exceptions. -
Mode Precision: Use
"r"for reading,"w"for overwriting/truncating, and"a"for appending new entries. -
UTF-8 Encoding: Pass
encoding="utf-8"explicitly to prevent multi-byte encoding corruption across operating systems. -
Structured Standards: Choose
CSVfor flat tabular matrices andJSONfor nested dictionary and list persistence.
Next Lesson: Errors, Exceptions & Debugging Mindset
Missing files, bad user input, and runtime flaws can crash your software! Master defensive error handling, try/except blocks, and professional debugging strategies.