Chapter 10: Files and Exceptions
Work with persistent data. Handle errors gracefully.
Reading Files
Reading Entire Contents
from pathlib import Path
path = Path('servers.txt')
contents = path.read_text()
print(contents)
The pathlib module handles OS-specific path differences.
Stripping Whitespace
read_text() adds trailing newline. Strip it:
contents = path.read_text().rstrip()
Reading Lines
path = Path('servers.txt')
contents = path.read_text()
for line in contents.splitlines():
print(f"Server: {line}")
File Paths
Relative (from script location):
path = Path('data/servers.txt')
Absolute:
path = Path('/etc/hosts')
Always use forward slashes - pathlib converts for Windows.
Writing Files
Writing Text
from pathlib import Path
path = Path('output.txt')
path.write_text("Server: web-01\nStatus: running")
write_text() overwrites existing content.
|
Writing Multiple Lines
Build the string first:
servers = ['web-01', 'web-02', 'db-01']
output = ""
for server in servers:
output += f"{server}\n"
path = Path('servers.txt')
path.write_text(output)
Or use join:
path.write_text('\n'.join(servers))
Exceptions
Errors during execution raise exceptions. Handle them to prevent crashes.
try-except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
Common Exceptions
| Exception | Cause |
|---|---|
|
File doesn’t exist |
|
Division by zero |
|
Wrong value type (e.g., |
|
Wrong type for operation |
|
Dict key doesn’t exist |
|
List index out of range |
FileNotFoundError
from pathlib import Path
path = Path('missing.txt')
try:
contents = path.read_text()
except FileNotFoundError:
print(f"File not found: {path}")
The else Block
Code that runs only if try succeeds:
path = Path('servers.txt')
try:
contents = path.read_text()
except FileNotFoundError:
print(f"Missing: {path}")
else:
lines = contents.splitlines()
print(f"Found {len(lines)} servers")
Failing Silently
Use pass to suppress errors:
try:
contents = path.read_text()
except FileNotFoundError:
pass # Silently ignore missing files
Multiple Files
files = ['web.conf', 'db.conf', 'cache.conf']
for filename in files:
path = Path(filename)
try:
contents = path.read_text()
except FileNotFoundError:
print(f"Missing: {filename}")
else:
print(f"Loaded: {filename}")
Missing files don’t stop processing of remaining files.
Checking File Existence
path = Path('config.txt')
if path.exists():
contents = path.read_text()
else:
print("Config not found, using defaults")
Working with JSON
JSON stores structured data. Perfect for configs and APIs.
Writing JSON
from pathlib import Path
import json
servers = {
'web-01': {'ip': '10.0.1.10', 'port': 443},
'db-01': {'ip': '10.0.2.10', 'port': 5432}
}
path = Path('servers.json')
contents = json.dumps(servers) (1)
path.write_text(contents)
| 1 | dumps = dump to string |
Result in servers.json:
{"web-01": {"ip": "10.0.1.10", "port": 443}, "db-01": {"ip": "10.0.2.10", "port": 5432}}
Pretty print:
contents = json.dumps(servers, indent=2)
Reading JSON
path = Path('servers.json')
contents = path.read_text()
servers = json.loads(contents) (1)
print(servers['web-01']['ip']) # 10.0.1.10
| 1 | loads = load from string |
Saving User Data
from pathlib import Path
import json
path = Path('config.json')
# Load existing or create new
if path.exists():
config = json.loads(path.read_text())
print(f"Welcome back, {config['username']}")
else:
username = input("Enter username: ")
config = {'username': username}
path.write_text(json.dumps(config))
print("Config saved")
Refactoring Example
Break complex code into functions:
from pathlib import Path
import json
def load_config(path):
"""Load config from JSON file."""
if path.exists():
return json.loads(path.read_text())
return None
def save_config(path, config):
"""Save config to JSON file."""
path.write_text(json.dumps(config, indent=2))
def get_username(path):
"""Get username, prompting if needed."""
config = load_config(path)
if config:
return config.get('username')
username = input("Enter username: ")
save_config(path, {'username': username})
return username
# Usage
path = Path('config.json')
username = get_username(path)
print(f"User: {username}")
Each function has one clear job.
Common Patterns
Log File Parser
from pathlib import Path
def count_errors(log_path):
"""Count ERROR lines in log file."""
path = Path(log_path)
try:
contents = path.read_text()
except FileNotFoundError:
return -1
errors = 0
for line in contents.splitlines():
if 'ERROR' in line:
errors += 1
return errors
Config File with Defaults
from pathlib import Path
import json
DEFAULTS = {
'port': 8080,
'debug': False,
'log_level': 'INFO'
}
def load_config(config_path):
"""Load config with defaults for missing keys."""
config = DEFAULTS.copy()
path = Path(config_path)
if path.exists():
user_config = json.loads(path.read_text())
config.update(user_config)
return config
Quick Reference
| Operation | Code |
|---|---|
Read file |
|
Write file |
|
Split lines |
|
Check exists |
|
Handle error |
|
Run on success |
|
Ignore error |
|
JSON to string |
|
String to JSON |
|
Exercises
10-1. Log Reader
Read a text file and print each line with line number.
10-2. Server List
Write a list of servers to a file, one per line. Read it back.
10-3. Error Handler
Write code that handles FileNotFoundError and ValueError differently.
10-4. JSON Config
Create a config file with server settings (host, port, timeout). Load and modify it.
10-5. Remember Me
Save user preferences to JSON. Load on next run and show "Welcome back."
10-6. Word Count
Count words in a text file. Handle missing file gracefully.
Summary
-
pathlib.Pathhandles file paths across platforms -
read_text()andwrite_text()for simple file operations -
try-exceptprevents crashes from errors -
elseruns after successfultry -
passfor silent error handling -
JSON for structured data:
dumps()to write,loads()to read -
Check
path.exists()before reading optional files
Next: Testing your code with pytest.