Files and Modules in Python
Reading and writing text files safely with with, working with CSV and JSON, handling paths, importing from the standard library, and splitting your own program into modules.
Why programs need files
Everything a program keeps in variables disappears when it ends. To remember data between runs (a to-do list, settings, scores) or to process data someone else produced, a program reads and writes files.
The built-in open(path, mode) returns a file object. The mode says what you intend to do:
"r": read (the default); the file must exist."w": write; creates the file, or empties an existing one first."a": append; writes at the end and keeps what is there.
Always open files inside a with statement. It closes the file automatically when the block ends, even if an exception occurs, so data is flushed to disk and the file is released. Also pass encoding="utf-8" for text, so the program behaves the same on every operating system.
Opening an existing file with "w" erases it immediately, before you write anything. Use "a" to add to the end.
1with open("notes.txt", "w", encoding="utf-8") as f:2 f.write("Buy milk\n")3 f.write("Call Ada\n")45with open("notes.txt", encoding="utf-8") as f:6 content = f.read()7print(content)8print(repr(content))
Buy milk Call Ada 'Buy milk\nCall Ada\n'
Reading a file line by line
f.read() returns the whole file as one string, which is fine for small files. For anything that might be large, loop over the file object itself: it yields one line at a time, so memory use stays small however big the file is.
Each line keeps its newline character at the end. line.rstrip("\n") (or line.strip() when surrounding spaces do not matter) removes it before you process the text. f.readlines() returns all lines as a list when you really need them all at once.
1with open("scores.txt", "w", encoding="utf-8") as f:2 f.write("ada 91\ngrace 88\n\nlinus 79\n")34total = 05with open("scores.txt", encoding="utf-8") as f:6 for number, line in enumerate(f, start=1):7 line = line.strip()8 if not line:9 continue10 name, score = line.split()11 total += int(score)12 print(number, name.title(), score)13print("Total:", total)
1 Ada 91 2 Grace 88 4 Linus 79 Total: 258
Writing and appending
f.write(text) writes exactly the text you give it and returns the number of characters written. It does not add a newline, so include \n yourself. print(..., file=f) is a convenient alternative that adds the newline and the spaces between values for you.
Append mode is the natural choice for logs: every run adds lines and nothing is lost.
1for run in range(1, 4):2 with open("log.txt", "a", encoding="utf-8") as log:3 print("run", run, "finished", file=log)45with open("log.txt", encoding="utf-8") as log:6 lines = log.readlines()7print(len(lines), "lines")8print(lines[-1].rstrip())
3 lines run 3 finished
CSV files with the csv module
CSV (comma-separated values) is the most common format for tables: one record per line, fields separated by commas. Splitting on commas yourself breaks as soon as a field contains a comma, which CSV handles by quoting the field ("Lovelace, Ada"). The standard csv module does the quoting and unquoting correctly.
csv.writer(f).writerow([...])writes a row;csv.DictWriterwrites dictionaries.csv.reader(f)yields each row as a list of strings;csv.DictReader(f)yields dictionaries keyed by the header line.
Open CSV files with newline="" as the csv documentation recommends, and remember that every field you read is a string: convert numbers yourself.
1import csv23rows = [["name", "score"], ["Lovelace, Ada", 91], ["Hopper, Grace", 88]]4with open("grades.csv", "w", newline="", encoding="utf-8") as f:5 csv.writer(f).writerows(rows)67with open("grades.csv", encoding="utf-8") as f:8 print(f.read().strip())910with open("grades.csv", newline="", encoding="utf-8") as f:11 for row in csv.DictReader(f):12 print(row["name"], "->", int(row["score"]) + 5)
name,score "Lovelace, Ada",91 "Hopper, Grace",88 Lovelace, Ada -> 96 Hopper, Grace -> 93
JSON: saving structured data
JSON stores nested data (dictionaries, lists, strings, numbers, booleans and null) as text. It is the format of most web APIs and a simple way to save a program's state.
json.dump(data, f)writes Python data to a file;json.load(f)reads it back.json.dumps(data)andjson.loads(text)do the same with strings.
Python's True, False and None become true, false and null. Tuples become lists, and dictionary keys always become strings. indent=2 makes the file readable for people.
1import json23state = {"player": "Ada", "level": 3, "inventory": ["map", "key"], "boss_defeated": False}4with open("save.json", "w", encoding="utf-8") as f:5 json.dump(state, f, indent=2)67with open("save.json", encoding="utf-8") as f:8 loaded = json.load(f)9print(loaded == state)10print(loaded["inventory"][1])11print(json.dumps({"ok": True, "score": None, 1: (2, 3)}))
True
key
{"ok": true, "score": null, "1": [2, 3]}Paths and missing files
The pathlib module represents file paths as objects: Path("data") / "report.csv" joins parts with the right separator for the operating system, and methods such as exists(), suffix, stem, read_text() and write_text() cover the common cases in one call.
A relative path such as "notes.txt" is resolved from the current working directory, the folder the program was started from, which is not necessarily the folder the script lives in. When a program cannot find its data file, this is usually why.
Opening a file that does not exist raises FileNotFoundError. Handle it where a sensible fallback exists, for example starting with an empty list the first time a program runs.
1import json2from pathlib import Path34report = Path("data") / "report.csv"5print(report.name, report.suffix, report.stem)67def load_tasks(path):8 try:9 with open(path, encoding="utf-8") as f:10 return json.load(f)11 except FileNotFoundError:12 return []1314print(load_tasks("tasks.json"))15Path("tasks.json").write_text('["learn files"]', encoding="utf-8")16print(load_tasks("tasks.json"))17print(Path("tasks.json").exists())
report.csv .csv report [] ['learn files'] True
Modules and the standard library
A module is a file of Python code that other code can import. Python ships with a large standard library of modules, so many problems are already solved before you install anything.
There are three common import forms:
import maththenmath.sqrt(2): the module name keeps it clear where a name comes from.from math import sqrtthensqrt(2): shorter, for names you use often.import statistics as st: an alias for long module names.
Avoid from module import *: it dumps every name into your file and makes it unclear where anything came from.
Standard library modules worth knowing early
| Module | Use it for |
|---|---|
| math | sqrt, floor, ceil, pi, gcd |
| random | random numbers, choice, shuffle (seed it for repeatable results) |
| datetime | dates, times, durations, formatting |
| collections | Counter, defaultdict, deque, namedtuple |
| statistics | mean, median, mode, stdev |
| csv / json | reading and writing data files |
| pathlib / os | paths, folders, environment |
1import statistics as st2from collections import Counter3from datetime import date, timedelta45scores = [72, 95, 88, 61, 88]6print(st.mean(scores), st.median(scores), st.mode(scores))78start = date(2026, 10, 20)9deadline = start + timedelta(days=30)10print(deadline.isoformat(), deadline.strftime("%A"))1112print(Counter("mississippi").most_common(2))
80.8 88 88
2026-11-19 Thursday
[('i', 4), ('s', 4)]Splitting your own program into modules
Any .py file is a module. As a program grows, move related functions into their own file and import them where they are needed. A typical small project looks like this:
expenses/
main.py # menu and input/output
storage.py # load and save the data file
reports.py # calculations and summaries
# reports.py
def total(expenses):
return sum(e["amount"] for e in expenses)
# main.py
from reports import total
import storage
expenses = storage.load("expenses.json")
print("Spent so far:", total(expenses))
Each file now has one responsibility, can be read on its own, and can be tested without running the whole program. Python finds reports.py because it sits in the same folder as the script being run.
if __name__ == "__main__"
When a module is imported, Python runs all of its top-level code. That is a problem if the file also contains a demo or a menu loop that should only run when the file is started directly.
Python sets the special variable __name__ to "__main__" for the file you run, and to the module's own name when it is imported. Putting the start-up code under if __name__ == "__main__": makes a file usable both as a program and as an importable module. Wrapping that code in a main() function keeps its variables out of the module's global names.
1def greet(name):2 return f"Hello, {name}!"34def main():5 print(greet("world"))67print("__name__ is", __name__)8if __name__ == "__main__":9 main()
__name__ is __main__ Hello, world!
Installing third-party packages
Beyond the standard library, thousands of packages are published on the Python Package Index (PyPI) and installed with pip: python -m pip install requests. Install them inside a virtual environment so each project has its own versions:
python -m venv .venv
.venv\Scripts\activate (Windows)
source .venv/bin/activate (macOS / Linux)
python -m pip install requests
Record what a project needs in a requirements.txt file (python -m pip freeze > requirements.txt) so someone else can recreate the same environment. Everything in this course uses only the standard library, so no installation is needed for the exercises.
- •Open files with with and an explicit encoding; the file closes itself.
- •"w" empties a file, "a" appends, "r" reads.
- •Use csv and json instead of parsing those formats by hand.
- •Split programs into modules with one responsibility each, and guard start-up code with if __name__ == "__main__".
Check your understanding
What happens when you open an existing file with mode "w"?
Practice
Read quoted CSV with the csv module, group a sales export like a data-analysis library would, and parse an HTML page with the standard library's html.parser.
CSV Grade Report
Reveal Solution & AnalysisHide Solution
Pandas-Style Sales Analysis
Reveal Solution & AnalysisHide Solution
Web Scraper: Extract Article Links
Reveal Solution & AnalysisHide Solution
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| Read a whole file | with open(p, encoding="utf-8") as f:
text = f.read() |
| Read line by line | for line in f:
line = line.rstrip("\n") |
| Write / append | open(p, "w") / open(p, "a") |
| CSV rows as dicts | for row in csv.DictReader(f): |
| Save / load JSON | json.dump(data, f, indent=2); data = json.load(f) |
| Join paths | Path("data") / "report.csv" |
| Import forms | import math; from math import sqrt; import statistics as st |
| Main guard | if __name__ == "__main__":
main() |
Common Pitfalls & Developer Mistakes
Forgetting to close a file (or not using with)
Why it happens: Written data may not reach the disk and the file stays locked on some systems.
Opening the data file with "w" to update it
Why it happens: The old contents are erased before you read them.
Comparing lines without removing the newline
Why it happens: "yes\n" == "yes" is False.
Naming your own file random.py, json.py or csv.py
Why it happens: Your file shadows the standard library module and imports break in confusing ways.