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

FileNotFoundError

File doesn’t exist

ZeroDivisionError

Division by zero

ValueError

Wrong value type (e.g., int('abc'))

TypeError

Wrong type for operation

KeyError

Dict key doesn’t exist

IndexError

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

Path('file').read_text()

Write file

Path('file').write_text(content)

Split lines

contents.splitlines()

Check exists

path.exists()

Handle error

try: …​ except ErrorType:

Run on success

else: block after except

Ignore error

pass in except block

JSON to string

json.dumps(data)

String to JSON

json.loads(string)

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.Path handles file paths across platforms

  • read_text() and write_text() for simple file operations

  • try-except prevents crashes from errors

  • else runs after successful try

  • pass for 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.