Chapter 10 - Reading and Writing Files (Python)
Here's a concise walkthrough of the main ideas in Chapter 10, each with a small example.
Files and filepaths
A file has a filename and a path; paths are made of folders (directories), with a root like C:\ on Windows or / on macOS/Linux.
from pathlib import Path
p = Path('C:/Users/Al/Documents/project.docx')
print(p.name) # 'project.docx'
print(p.parent) # 'C:/Users/Al/Documents'
Standardizing path separators (pathlib.Path)
Use Path() instead of hard-coding \ or /; it normalizes separators per OS.
from pathlib import Path
p = Path('spam', 'bacon', 'eggs')
print(p) # e.g. 'spam/bacon/eggs'
print(str(p)) # OS-native string path
Joining paths with / on Path
The / operator joins Path objects and strings into new paths.
from pathlib import Path
p = Path('spam') / 'bacon' / 'eggs'
print(p) # 'spam/bacon/eggs'
You must have a Path on the left side (not 'spam' / 'bacon').
Current working directory
Path.cwd() gives current working directory; os.chdir() changes it.
from pathlib import Path
import os
print(Path.cwd())
os.chdir('C:/Windows/System32')
print(Path.cwd())
Home directory
Path.home() returns the user's home folder; good default base for your own files.
from pathlib import Path
home = Path.home()
print(home) # e.g. 'C:/Users/YourName'
config = home / 'myapp' / 'config.json'
Absolute vs relative paths
- Absolute starts at root (
C:\or/). - Relative is interpreted from current working directory;
.= current,..= parent.
from pathlib import Path
print(Path('spam/bacon/eggs').is_absolute()) # False
print((Path.cwd() / 'spam/bacon/eggs').absolute())
Creating directories
os.makedirs(path)creates nested folders.Path(...).mkdir(parents=True)does likewise.
import os
from pathlib import Path
os.makedirs('C:/delicious/walnut/waffles', exist_ok=True)
Path('C:/Users/Al/spam').mkdir(parents=True, exist_ok=True)
Path parts: anchor, parent, name, stem, suffix, drive, parts, parents
You can pull apart a path into its components.
from pathlib import Path
p = Path('C:/Users/Al/spam.txt')
print(p.anchor) # 'C:\\'
print(p.parent) # 'C:/Users/Al'
print(p.name) # 'spam.txt'
print(p.stem) # 'spam'
print(p.suffix) # '.txt'
print(p.parts) # ('C:\\', 'Users', 'Al', 'spam.txt')
print(Path.cwd().parents[0]) # parent
File stats: size and timestamps
Path.stat() returns size and times (st_size, st_mtime, etc.).
from pathlib import Path
import time
f = Path('C:/Windows/System32/calc.exe')
st = f.stat()
print(st.st_size) # bytes
print(time.asctime(time.localtime(st.st_mtime)))
Glob patterns with Path.glob()
Use * and ? to match filenames (simplified patterns).
from pathlib import Path
desktop = Path('C:/Users/Al/Desktop')
for path in desktop.glob('*.txt'):
print(path)
Checking path existence / type
p.exists()– path exists.p.is_file()– existing file.p.is_dir()– existing directory.
from pathlib import Path
p = Path('C:/Windows')
print(p.exists(), p.is_dir()) # True, True
f = Path('C:/Windows/System32/calc.exe')
print(f.is_file()) # True
Basic text file helpers: read_text() and write_text()
Path.read_text() and Path.write_text() are convenience methods for text files.
from pathlib import Path
p = Path('spam.txt')
p.write_text('Hello, world!')
print(p.read_text()) # 'Hello, world!'
The three-step file I/O pattern with open()
Plaintext file flow:
open(path, mode, encoding)→ file objectfile.read()/file.write()file.close()
from pathlib import Path
hello_file = open(Path.home() / 'hello.txt', encoding='UTF-8')
content = hello_file.read()
hello_file.close()
print(content)
Reading whole file: read()
file.read() returns the entire file contents as a single string.
f = open('hello.txt', encoding='UTF-8')
text = f.read()
f.close()
print(text)
Reading by lines: readlines()
file.readlines() returns a list of lines, including \n.
from pathlib import Path
sonnet_file = open(Path.home() / 'sonnet29.txt', encoding='UTF-8')
lines = sonnet_file.readlines()
sonnet_file.close()
print(lines[0]) # first line with '\n'
Writing files: write vs append modes
'w'(write) overwrites or creates.'a'(append) adds to end.write()returns number of characters written, no automatic\n.
# Write (overwrite)
bacon_file = open('bacon.txt', 'w', encoding='UTF-8')
bacon_file.write('Hello, world!\n')
bacon_file.close()
# Append
bacon_file = open('bacon.txt', 'a', encoding='UTF-8')
bacon_file.write('Bacon is not a vegetable.')
bacon_file.close()
Using Path with open()
You can pass a Path directly to open().
from pathlib import Path
p = Path.home() / 'data.txt'
with open(p, 'w', encoding='UTF-8') as f:
f.write('test')
with statements (context managers) for files
with open(...) as f: automatically closes the file when leaving the block.
with open('data.txt', 'w', encoding='UTF-8') as f:
f.write('Hello, world!')
with open('data.txt', encoding='UTF-8') as f:
content = f.read()
Saving Python data with shelve
shelve stores Python objects (like a persistent dict) in binary files.
import shelve
with shelve.open('mydata') as shelf_file:
shelf_file['cats'] = ['Zophie', 'Pooka', 'Simon']
Later:
with shelve.open('mydata') as shelf_file:
print(shelf_file['cats']) # ['Zophie', 'Pooka', 'Simon']
print(list(shelf_file.keys())) # ['cats']
Project: Generate Random Quiz Files
Goal: generate multiple quiz/answer files using file I/O and randomization.
Key ideas:
- Store data in a dict (states → capitals).
- For each quiz file: randomize question order, write MC questions.
- For each answer key: write correct letters.
Tiny skeleton:
import random
from pathlib import Path
capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau'}
quiz_dir = Path('quizzes')
quiz_dir.mkdir(exist_ok=True)
for quiz_num in range(1, 3):
with open(quiz_dir / f'capitals_quiz_{quiz_num}.txt', 'w', encoding='UTF-8') as quiz_file, \
open(quiz_dir / f'capitals_quiz_answers_{quiz_num}.txt', 'w', encoding='UTF-8') as ans_file:
states = list(capitals.keys())
random.shuffle(states)
for q_num, state in enumerate(states, start=1):
correct = capitals[state]
wrong = list(capitals.values())
wrong.remove(correct)
options = random.sample(wrong, k=min(3, len(wrong))) + [correct]
random.shuffle(options)
quiz_file.write(f'{q_num}. What is the capital of {state}?\n')
for i, opt in enumerate(options):
quiz_file.write(f" {'ABCD'[i]}. {opt}\n")
quiz_file.write('\n')
ans_file.write(f'{q_num}. {"ABCD"[options.index(correct)]}\n')
Overall idea of the chapter
This chapter's pattern is: use Path for safe paths, open/with for I/O, and shelve for saving Python objects.