AmouAI Hub/Courses/Programming Fundamentals/Day 10
Files, Modules & Mini-Project 2
Programs that outlive their own run: reading and writing files, and splitting code across modules.
By the end of today you can
- Read and write text files, and say why
withis not optional - Choose the right mode —
r,w,a— and know whatwdestroys - Handle a missing file deliberately rather than crashing
- Read and write CSV without hand-rolling a parser
- Split a program across modules and import between them
- Explain what
if __name__ == "__main__":actually does - Find and use the right standard-library module
- Deliver Mini-Project 2
▶Today's videos
Watch each video, then work the matching sections below. Watching alone will not do it.
w destroys · why encoding is not optional · handling a missing file deliberately · CSV with DictReader, and why you must not write your own parser.import * is banned · what if __name__ == "__main__" actually does · a tour of the standard library · the full Contact Book build.1with, and why
Every program you have written so far forgot everything the moment it ended.
# writing
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("first line\n")
f.write("second line\n")
# reading, whole file
with open("notes.txt", encoding="utf-8") as f:
content = f.read()
# reading, line by line — the memory-safe way
with open("notes.txt", encoding="utf-8") as f:
for line in f:
print(line.rstrip("\n"))with is actually doingIt guarantees the file is closed — even if an exception is raised inside the block. Without it, a crash halfway through leaves the file open and the last writes possibly unflushed. with is not a style preference; it is the difference between reliable and nearly reliable.
| Mode | Does | Careful |
|---|---|---|
r | Read. The default. | FileNotFoundError if it does not exist |
w | Write | Truncates an existing file to zero bytes immediately |
a | Append | Creates the file if needed; never destroys |
x | Create | Fails if the file already exists — useful as a guard |
w deletes before it writesOpening an existing file with "w" empties it the instant open() returns — before you have written a single byte. If your program then crashes, the original is gone. When in doubt use "a", or write to a temporary file and rename.
Always pass encoding
encoding="utf-8" is not optional in practice. Without it Python uses whatever the operating system prefers, so a file that reads perfectly on your machine raises UnicodeDecodeError on someone else's. State it every time.
Missing files
import os
# ask first
if os.path.exists("config.txt"):
with open("config.txt", encoding="utf-8") as f:
settings = f.read()
else:
settings = ""
# or try, and handle the failure — this is Day 15's territory
try:
with open("config.txt", encoding="utf-8") as f:
settings = f.read()
except FileNotFoundError:
settings = ""2Structured files: CSV
Do not write your own parser. Really.
A comma-separated file looks trivial to parse with .split(",") — and it is, until one field contains a comma, or a quote, or a newline. All three happen in real data. The standard library already solved this.
import csv
with open("scores.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["score"])import csv
rows = [{"name": "Amin", "score": 88}, {"name": "Ada", "score": 95}]
with open("out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "score"])
writer.writeheader()
writer.writerows(rows)newline="" is required, not decorativeThe csv module does its own line-ending handling. Omit newline="" and on Windows you get a blank line between every row. It is in the documentation and everybody skips it once.
Note that everything DictReader gives you is a string — row["score"] is "88", not 88. Converting, and deciding what to do when the conversion fails, is your job. That is Mini-Project 2's real content.
3Modules
One file stops being enough at about 300 lines.
A module is just a .py file. Importing it runs it once and gives you its names.
| Import style | Use when |
|---|---|
import stats | Default. The stats. prefix says where the name came from. |
from stats import mean_of | You use it constantly and the name is unambiguous. |
from stats import * | Never. It hides where names came from and shadows silently. |
The line everybody copies without understanding
def main():
print("running as a program")
if __name__ == "__main__":
main()Python sets __name__ to "__main__" in the file you ran, and to the module's own name in anything you imported. So the guard means: run this only when I am the program, not when I am being imported.
Without the guard, import stats would execute everything at the bottom of stats.py — prompting for input, printing a report, overwriting a file. The guard is what lets one file be both a runnable program and an importable library.
4The standard library
Most of what you are about to write already exists.
| Module | For | One thing worth knowing |
|---|---|---|
pathlib | Paths and files | Path("a")/"b" beats string concatenation everywhere |
csv | Tabular text | DictReader gives you named columns |
json | Structured data | json.dump/load — the format every API speaks |
collections | Better containers | Counter, defaultdict — yesterday's patterns, built in |
datetime | Dates and times | Never do date arithmetic by hand |
random | Randomness | seed() makes a random program reproducible for tests |
math | Maths | isclose() is how you compare floats |
os / sys | The environment | sys.argv for command-line arguments |
from collections import Counter
from pathlib import Path
import json
# yesterday's counting pattern, in one line
print(Counter("abracadabra").most_common(3))
# paths that work on every operating system
p = Path("data") / "scores.csv"
print(p.suffix, p.stem, p.exists())
# structured data, saved and reloaded
record = {"name": "Amin", "scores": [88, 95]}
text = json.dumps(record)
print(text)
print(json.loads(text)["scores"])Before writing a helper, spend sixty seconds asking whether the standard library already has it. It usually does, it is better tested than yours will be, and the next person reading your code already knows it.
5Mini-Project 2 — Contact Book
Your second graded deliverable · 100 points
Build a contact book that survives being closed. Everything in Weeks 1 and 2 comes together here: functions, lists, dictionaries, files and modules.
Required behaviour
- A menu loop: add, list, search, delete, quit.
- Each contact has a name, a phone number and an email.
- Contacts persist to
contacts.csvand reload on the next run. - Search matches part of a name, case-insensitively.
- Adding a duplicate name asks whether to update the existing entry.
- Bad input never crashes the program — including a missing or malformed CSV file.
- The code is split across at least two modules, with an importable one containing no I/O.
| Criterion | Points |
|---|---|
| Menu loop and clean exit | 10 |
| Add, list, search, delete all correct | 25 |
| Persistence — saves and reloads correctly | 20 |
| Handles missing / malformed / empty file | 15 |
| Sensible module split; importable module does no I/O | 15 |
| Naming, constants, docstrings, PEP 8 | 10 |
| Comments where they earn their place | 5 |
It is not the file handling. It is that you must decide what happens when the data on disk does not match what your code expects — a missing column, a blank line, a half-written file from a crash. Deciding that deliberately, and writing it down, is the assignment.
Suggested build order
- Hard-code three contacts in a list of dicts. Get list and search working with no files at all.
- Add the menu loop around it.
- Add saving. Check the CSV by opening it in a text editor.
- Add loading. Now quit and restart — this is the moment the project becomes real.
- Break it deliberately: delete the file, empty it, corrupt a row, add a stray blank line. Fix what breaks.
- Split into modules. Run
blackandruff. Read it once as a stranger.
Get every operation right against a hard-coded list before a single file is opened. If something is wrong then, it is definitely the logic — there is nothing else in the program yet. It is the same discipline as Mini-Project 1, and it works for the same reason.
Keep this file too. On Day 12 you will turn each contact from a dictionary into an object, and on Day 15 you will write tests for it. Having the earlier version to compare against is, as ever, the lesson.
>_Python playground
A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.
input(), comma separated →
✓Exercise set
Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.
Day 11 — Algorithm Design & Analysis
Derive binary search, implement three sorts by hand, and read Big-O honestly.