10xdev Book: Programming from Zero to Pro with Python
Chapter 8: File Handling in Python: Reading, Writing, and pathlib
Chapter Introduction
Okay, you’ve mastered organizing data within your program using lists, tuples, and dictionaries. But what happens when you close the program? Poof! 💨 Everything stored in memory vanishes.
To make your data persistent – meaning it sticks around even after the program stops – you need to save it to files on your computer’s hard drive. File handling lets your Python programs read information from files and write information to them.
This chapter covers the essential skill of file I/O (Input/Output). We’ll learn:
- The modern, safe way to open and close files using
with open(). - Different modes for reading (
'r'), writing ('w'), and appending ('a'). - How to read file contents.
- How to write data to files.
- Why using the
pathlibmodule is the best practice for handling file paths.
1. Opening and Closing Files: The with open() Way
The first step is always to open the file you want to work with. The modern, highly recommended way to do this in Python is using the with open() statement.
Why with open()?
It automatically takes care of closing the file for you, even if errors occur within the block. This prevents data corruption or files being left locked. It’s the clean, safe, and professional approach.
Basic Structure:
# 'file_path' is the path to your file (can be a string or a Path object)
# 'mode' specifies what you want to do ('r', 'w', 'a')
# 'encoding="utf-8"' is VITAL for handling text correctly, especially non-English chars
# 'f' is a common variable name for the file object (like a remote control)
file_path = "my_notes.txt" # Simple filename in the same directory
try:
with open(file_path, mode='w', encoding='utf-8') as f:
# --- Code that works with the file 'f' goes INSIDE this block ---
f.write("This is the first line.\n")
f.write("This is the second line.\n")
# --- End of the block ---
# Once the 'with' block ends, the file is automatically closed!
print(f"Successfully wrote to {file_path}")
except IOError as e:
print(f"Error working with file {file_path}: {e}")
2. File Modes: Read, Write, Append
The mode argument tells Python how you intend to interact with the file:
'r'(Read - Default): Opens for reading text. Error if the file doesn’t exist.'w'(Write): Opens for writing text. ⚠️ Overwrites the entire file if it exists! Creates the file if it doesn’t.'a'(Append): Opens for writing text, adding new content to the end of the file. Creates the file if it doesn’t exist.'rb','wb','ab'(Binary modes): Use these when dealing with non-text files like images or executables. Addbto the mode.
3. Writing to Files
Use the .write() method of the file object. Remember that .write() does not automatically add a newline. You need to include \n yourself if you want lines separated.
lines_to_write: list[str] = ["First line.", "Second line.", "Third line."]
output_file = "output.txt"
try:
with open(output_file, mode='w', encoding='utf-8') as f:
for line in lines_to_write:
f.write(line + "\n") # Add the newline character manually
print(f"Wrote {len(lines_to_write)} lines to {output_file}")
except IOError as e:
print(f"Error writing to {output_file}: {e}")
4. Reading from Files
There are several ways to read:
.read(): Reads the entire file content into a single string. Good for small files, but inefficient for very large ones.input_file = "output.txt" # Assuming this exists from the previous example try: with open(input_file, mode='r', encoding='utf-8') as f: content: str = f.read() print("\n--- File Content (using .read()) ---") print(content) print("--- End of Content ---") except FileNotFoundError: print(f"Error: File '{input_file}' not found.") except IOError as e: print(f"Error reading {input_file}: {e}")- Looping over the file object (Recommended for text files): Reads the file line by line. Memory efficient for large files.
try: with open(input_file, mode='r', encoding='utf-8') as f: print("\n--- Reading line by line ---") for line in f: print(f"Line: {line.strip()}") # .strip() removes leading/trailing whitespace (incl. \n) except FileNotFoundError: print(f"Error: File '{input_file}' not found.") except IOError as e: print(f"Error reading {input_file}: {e}") .readlines(): Reads all lines into a list of strings, with each string ending in\n. Can use a lot of memory for large files.try: with open(input_file, mode='r', encoding='utf-8') as f: all_lines: list[str] = f.readlines() print(f"\nRead {len(all_lines)} lines into a list.") if len(all_lines) > 1: print(f"The second line was: {all_lines[1].strip()}") except FileNotFoundError: print(f"Error: File '{input_file}' not found.") except IOError as e: print(f"Error reading {input_file}: {e}")
5. Handling Paths the Modern Way: pathlib 🌳
Hardcoding file paths as strings (like "my_folder/my_file.txt") can cause problems, especially when moving between Windows (which uses \) and Mac/Linux (which use /). The pathlib module (part of the standard library) provides an object-oriented way to handle paths reliably.
from pathlib import Path
# Get the current working directory
current_dir: Path = Path.cwd()
print(f"Current directory: {current_dir}")
# Create a path object for a file (doesn't create the file yet)
# Using / operator joins path components correctly on any OS
data_file_path: Path = current_dir / "data" / "user_data.csv"
print(f"Full path to data file: {data_file_path}")
# Get parts of the path
print(f"Filename: {data_file_path.name}") # -> user_data.csv
print(f"Parent folder: {data_file_path.parent}") # -> .../current_dir/data
print(f"File extension: {data_file_path.suffix}") # -> .csv
# Check if a file or directory exists
print(f"Does the file exist? {data_file_path.exists()}")
print(f"Is it a file? {data_file_path.is_file()}")
# Create parent directories if they don't exist
data_file_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Ensured directory '{data_file_path.parent}' exists.")
# Use the Path object directly with open()
try:
with open(data_file_path, mode='w', encoding='utf-8') as f:
f.write("UserID,Name\n")
f.write("1,Alice\n")
print(f"Successfully wrote to {data_file_path}")
except IOError as e:
print(f"Error writing: {e}")
Using pathlib makes your file operations more robust and operating-system independent. Get in the habit of using it!
6. Applied Project: Simple Notebook (Refined)
Let’s update our notebook app from Chapter 8 to use pathlib.
# --- Simple Note-Taking Program (with pathlib) ---
from pathlib import Path
import datetime
# Define the filename using pathlib
NOTES_FILENAME = "notes.txt"
notes_path = Path(NOTES_FILENAME)
# --- Function to add a note ---
def add_note():
note_text = input("Please enter your note: ").strip()
if not note_text:
print("Note cannot be empty.")
return
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
formatted_note = f"[{timestamp}] {note_text}\n"
try:
# Open using the Path object in append mode
with open(notes_path, mode='a', encoding='utf-8') as f:
f.write(formatted_note)
print("Note saved successfully!")
except IOError as e:
print(f"Error saving note: {e}")
# --- Function to view notes ---
def view_notes():
if not notes_path.exists():
print("No notes found. Add a note first.")
return
print("\n--- Your Notes ---")
try:
with open(notes_path, mode='r', encoding='utf-8') as f:
notes_content = f.read()
if not notes_content.strip():
print("Your notebook is empty.")
else:
print(notes_content)
except IOError as e:
print(f"Error reading notes: {e}")
# --- Main loop ---
while True:
print("\n--- Notebook Menu ---")
print("1. Add Note")
print("2. View Notes")
print("3. Exit")
choice = input("Enter choice: ").strip()
if choice == '1':
add_note()
elif choice == '2':
view_notes()
elif choice == '3':
print("Goodbye!")
break
else:
print("Invalid choice.")
7. Chapter Summary
- Always use
with open(...)for safe file handling; it guarantees closure. - Choose the correct mode:
'r'(read),'w'(write/overwrite),'a'(append). Add'b'for binary files. encoding='utf-8'is essential for text files.- Use
.write(string)to write (remember\n). - Use
.read(),for line in file:, or.readlines()to read. - Use the
pathlibmodule (Pathobjects) for reliable, cross-platform path manipulation. It’s the modern standard.
Working with external resources like files inevitably brings the risk of errors (
FileNotFoundError,IOError, etc.). Just printing an error message isn’t always enough. Next, we’ll learn how to handle these exceptions more systematically to build truly robust Python 3.14 applications.