Skip to main content

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:

  1. open(path, mode, encoding) → file object
  2. file.read() / file.write()
  3. 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.