SECTION 1
Historical Context & Motivation
Before graphical user interfaces and cloud storage existed, computers communicated with the outside world almost entirely through files. Early programmers fed instructions to machines on punched cards—physical cards with holes that represented data. The computer would read these cards, crunch numbers, and then punch results onto new cards or print them to paper. This fundamental pattern of reading input, processing it, and producing output is the backbone of nearly every program ever written.
1890s • Punched Card Systems
1960s • Magnetic Tape & Disk Files
1972 • The C Language & stdio
1991 • Python Is Born
2020s • Data Pipelines Everywhere
Today, whether you are building a grade calculator that reads student scores from a spreadsheet or a script that generates a report, you are using the same input-process-output pattern that has driven computing for over a century. The central question this lesson addresses is: How do you write a Python program that reliably reads data from a file, transforms it, writes results to a new file, and clearly documents the assumptions behind your design?
SECTION 2
Core Principles of File I/O
File I/O in Python revolves around a few essential ideas. Understanding these principles gives you a mental framework you can apply to any file-handling task, whether you are reading a simple text file or generating a complex report. Let's break these core ideas down.
Open → Use → Close
with statement handles closing automatically.File Modes
"r" (read), "w" (write—creates or overwrites), and "a" (append—adds to the end).Text vs. Binary
Assumptions & Documentation
Error Handling
KEY TAKEAWAY
with statement is like an automatic return slip—it guarantees the book goes back on the shelf.SECTION 3
Visualizing the File I/O Pipeline
The diagram below shows the complete lifecycle of a file I/O program. Data flows from an input file through your Python program's processing logic, and then out to an output file. Notice how assumptions are documented at the top and error handling wraps the entire process.
Notice the three distinct zones in the diagram. The input file on the left contains raw data in a specific format—in this case, comma-separated name-score pairs. The Python program in the center performs a sequence of transformations: reading each line, splitting it at the comma, converting the score to an integer, and computing an average. Finally, the output file on the right holds the processed results in a new, more useful format. The dashed boxes remind us that every robust program documents its assumptions up front and wraps file operations in error-handling blocks.
SECTION 4
How File I/O Works in Python
Python provides a clean, readable syntax for working with files. The built-in open() function is the gateway to all file operations. It returns a file object—a special object that gives you methods for reading or writing. Let's examine the key patterns.
Reading a File
READING PATTERN
filename — a string path to the file; "r" — read mode; f — the file object; f.read() — reads the entire file as one string. Alternatives: f.readline() reads one line, f.readlines() returns a list of all lines.
Writing to a File
WRITING PATTERN
"w" — write mode (creates a new file or overwrites an existing one); f.write(text) — writes the string text to the file. Use "a" mode instead if you want to append without erasing existing content.
The with Statement
CONTEXT MANAGER
The with keyword creates a context manager that guarantees the file closes when the indented block ends—even if an error occurs. This is the recommended way to handle files in Python.
Iterating Line by Line
LINE-BY-LINE READING
A file object is iterable, meaning you can loop through it directly. Each iteration returns the next line as a string. The .strip() method removes leading and trailing whitespace, including the newline character \n at the end of each line.
Important: Write Mode Erases!
"w" mode will completely erase the existing contents of that file before writing. If you accidentally open your input file in write mode, you will lose all the data. Always double-check your mode string before running your program.SECTION 5
Documenting Assumptions
Every file-based program makes assumptions about the data it works with. A program that reads student grades might assume each line has exactly two values separated by a comma, that scores are integers between 0 and 100, and that the file uses UTF-8 encoding. If any of these assumptions are wrong, the program could crash or produce incorrect results. Documenting your assumptions clearly is just as important as writing the code itself, because it helps anyone who uses or maintains your program understand what inputs it expects.
Let's look at the five key zones in the diagram above. At the very top of your file, a module docstring (a triple-quoted string) describes what the program does in one or two sentences. Below that, numbered assumption comments spell out exactly what your program expects about the input—file format, data types, delimiters, and file location. Named constants (written in ALL_CAPS by convention) make it easy to change filenames or settings without hunting through your code. Each function gets its own docstring explaining what it does. Finally, error handling with try/except catches predictable problems and displays helpful messages.
- ✓ File format — What type of file is it? (CSV, plain text, tab-separated)
- ✓ Encoding — Is it UTF-8, ASCII, or something else?
- ✓ Delimiter — What separates the values on each line? (comma, tab, space)
- ✓ Data types — Are values strings, integers, or floats?
- ✓ File location — Is the file in the same folder as the script, or somewhere else?
- ✓ Header row — Does the first line contain column names or actual data?
SECTION 6
Worked Example: Grade Report Generator
Let's build a complete program that reads student scores from a CSV file, calculates letter grades, and writes a formatted report to a new file. We will follow all of the best practices discussed so far: using with statements, documenting assumptions, handling errors, and using clear variable names.
Grade Report Generator
- 1
Step 1 — Define Assumptions and Constants
Start your program with a module docstring and explicit assumption comments. Define constants for the input and output filenames so they are easy to change later."""Grade Report Generator — Reads scores.csv and writes report.txt.""" # Assumption 1: scores.csv is in the same directory as this script # Assumption 2: Each line has format: Name,Score # Assumption 3: Scores are integers from 0 to 100 # Assumption 4: File uses UTF-8 encoding with no header row INPUT_FILE = "scores.csv" OUTPUT_FILE = "report.txt"Four assumptions documented; two constants defined. - 2
Step 2 — Write a Function to Read the Input File
Create a function that opens the input file, reads each line, splits it at the comma, and returns a list of (name, score) tuples. Include a docstring.def read_scores(filename): """Read CSV file, return list of (name, score) tuples.""" scores = [] with open(filename, "r") as f: for line in f: name, value = line.strip().split(",") scores.append((name, int(value))) return scoresFunction returns: [("Alice", 92), ("Bob", 85), ("Carol", 78)] - 3
Step 3 — Write a Function to Assign Letter Grades
Create a helper function that converts a numeric score to a letter grade using standard thresholds.def get_letter_grade(score): """Return letter grade for a numeric score (0-100).""" if score >= 90: return "A" elif score >= 80: return "B" elif score >= 70: return "C" elif score >= 60: return "D" else: return "F"get_letter_grade(92) → "A", get_letter_grade(85) → "B" - 4
Step 4 — Write a Function to Generate the Report
Create a function that takes the list of scores, formats each entry, calculates the class average, and writes everything to the output file.def write_report(filename, scores): """Write formatted grade report to a text file.""" total = sum(score for _, score in scores) average = total / len(scores) with open(filename, "w") as f: f.write("GRADE REPORT\n") f.write("=" * 30 + "\n") for name, score in scores: grade = get_letter_grade(score) f.write(f"{name}: {score} ({grade})\n") f.write("=" * 30 + "\n") f.write(f"Class Average: {average:.1f}\n")Output file contains formatted report with letter grades and average. - 5
Step 5 — Tie It Together with Error Handling
In the main section of your program, call the functions inside a try/except block to handle missing files or invalid data gracefully.try: data = read_scores(INPUT_FILE) write_report(OUTPUT_FILE, data) print(f"Report written to {OUTPUT_FILE}") except FileNotFoundError: print(f"Error: {INPUT_FILE} not found.") except ValueError: print("Error: Invalid data format in input file.")Program either writes report.txt successfully or prints a clear error message.
KEY TAKEAWAY
SECTION 7
Strengths, Limitations & Common Pitfalls
File I/O is incredibly useful, but it comes with trade-offs. Understanding the strengths and limitations helps you decide when file-based programs are the right tool for the job and when you might need a more advanced approach like a database.
| Aspect | Strengths | Limitations |
|---|---|---|
| Simplicity | Text files are easy to create, inspect, and share. You can open them in any text editor. | No built-in structure—you must parse and validate the format yourself. |
| Portability | Text files work on every operating system without special software. | Large files can be slow to process because the whole file may need to be read. |
| Data Persistence | Data saved to a file survives after the program closes, unlike variables stored in RAM. | Multiple programs writing to the same file simultaneously can cause data corruption. |
| Error Proneness | Python's try/except makes it straightforward to catch and recover from file errors. | Using wrong modes (e.g., "w" instead of "a") can accidentally erase data permanently. |
| Scalability | Perfect for small to medium datasets (thousands of records). | For millions of records, a database is more efficient and supports searching. |
Strengths vs. limitations of text-based file I/O
Common Pitfalls
- Forgetting .strip() — Each line read from a file includes a newline character ( ). If you don't strip it, string comparisons and conversions may fail silently.
- Opening input as "w" — This erases the file immediately. Always verify your mode before running the program.
- Hardcoding filenames inside functions — Pass filenames as parameters instead. This makes your functions reusable.
- Skipping error handling — If the file doesn't exist, your program crashes with a traceback. A try/except turns this into a friendly message.
KEY TAKEAWAY
SECTION 8
Connection to Advanced Topics
The file I/O skills you are learning now form the foundation for many advanced programming topics. As you progress in computer science, you will encounter more powerful ways to store and process data, but they all build on the same core principles of opening, reading, writing, and closing data streams.
| This Course (File I/O Basics) | Advanced Topic |
|---|---|
| Reading plain text files line by line | Parsing structured formats like JSON, XML, and YAML with specialized libraries |
| Writing formatted output to .txt files | Generating HTML reports, PDF documents, or Excel spreadsheets with libraries like pandas |
| Using try/except for file errors | Custom exception classes and logging frameworks for production applications |
| Storing data in CSV files | Using relational databases (SQL) or NoSQL databases for scalable data storage |
| Documenting assumptions in comments | Writing formal specifications, unit tests, and data validation schemas |
How file I/O basics connect to advanced programming concepts
One particularly exciting next step is the csv module built into Python. While we have been splitting lines manually at commas, the csv module handles tricky cases like values that contain commas inside quotation marks. Similarly, the json module lets you read and write structured data that web APIs use to communicate. These are natural extensions of the skills you are building right now, and you will likely encounter them in a second programming course or in a data science elective.
Looking Ahead
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
"r" mode, "w" mode, and "a" mode. Why is it dangerous to accidentally use "w" on your input file?Show answer
"r") opens an existing file for reading without modifying it. Write mode ("w") creates a new file or completely erases an existing file's contents before writing. Append mode ("a") opens a file and adds new content to the end without erasing what's already there. Using "w" on your input file would delete all the original data the instant the file is opened, before you have a chance to read any of it. This is one of the most common and devastating mistakes in file I/O programming.Question 2: BASIC CALCULATION
temps.txt that contains one temperature per line (e.g., 72, 68, 75, 80), write Python code that reads all the temperatures and prints the average. Use a with statement.Show answer
with open("temps.txt", "r") as f:
temps = [int(line.strip()) for line in f]
average = sum(temps) / len(temps)
print(f"Average temperature: {average:.1f}")
The with statement opens the file in read mode and automatically closes it. A list comprehension reads each line, strips whitespace, and converts the string to an integer. The average is calculated by dividing the sum by the number of entries. Note that .strip() is essential to remove the newline character from each line before converting to an integer.Question 3: INTERMEDIATE
students.csv contains lines in the format Name,Score1,Score2,Score3. Write a program that reads this file and writes a new file called averages.txt where each line shows the student's name and their average score. Include assumption comments in your solution.Show answer
# Assumption: students.csv has no header row
# Assumption: Each line has format Name,Score1,Score2,Score3
# Assumption: All scores are valid integers
with open("students.csv", "r") as infile:
with open("averages.txt", "w") as outfile:
for line in infile:
parts = line.strip().split(",")
name = parts[0]
scores = [int(s) for s in parts[1:]]
avg = sum(scores) / len(scores)
outfile.write(f"{name}: {avg:.1f}\n")
This program nests two with statements to keep both files open simultaneously. The input line is split at commas, the first element becomes the name, and the rest are converted to integers. The average is computed and written to the output file with one decimal place. The three assumption comments at the top document the expected input format.Question 4: APPLIED
inventory.csv contains lines like Widget,50,2.99 (product name, quantity, unit price). Write a complete program with documented assumptions that reads this file, calculates the total value of each product (quantity × price), and writes a report to inventory_report.txt showing each product's total value and a grand total at the bottom. Include error handling.Show answer
"""Inventory Report Generator"""
# Assumption 1: inventory.csv is in the same directory
# Assumption 2: Format is Product,Quantity,Price (no header)
# Assumption 3: Quantity is an integer, Price is a float
# Assumption 4: File uses UTF-8 encoding
INPUT_FILE = "inventory.csv"
OUTPUT_FILE = "inventory_report.txt"
try:
grand_total = 0
lines_out = []
with open(INPUT_FILE, "r") as f:
for line in f:
name, qty, price = line.strip().split(",")
total = int(qty) * float(price)
grand_total += total
lines_out.append(f"{name}: {int(qty)} x ${float(price):.2f} = ${total:.2f}")
with open(OUTPUT_FILE, "w") as f:
f.write("INVENTORY REPORT\n")
f.write("=" * 40 + "\n")
for item in lines_out:
f.write(item + "\n")
f.write("=" * 40 + "\n")
f.write(f"Grand Total: ${grand_total:.2f}\n")
print(f"Report saved to {OUTPUT_FILE}")
except FileNotFoundError:
print(f"Error: {INPUT_FILE} not found.")
except ValueError:
print("Error: Invalid data in input file.")
This solution demonstrates all best practices: a module docstring, four numbered assumptions, named constants, formatted output, and a try/except block that catches both missing-file and data-format errors. The grand total accumulates as each product line is processed.Question 5: CRITICAL THINKING
data.txt and writes results to data.txt (the same file). They claim this is fine because they read the data first, then write. However, sometimes their program produces an empty file. Explain what might be going wrong, why it's unreliable even when it seems to work, and propose a better approach that safely handles this scenario.Show answer
"r" mode, reads the data, closes it, and then opens it in "w" mode to write, this works in theory—but only if both operations complete without error. If the program crashes after opening in "w" mode but before writing, the original data is lost because write mode erases on open. If the classmate accidentally opens the file in "w" mode before reading, the data is destroyed immediately. A much safer approach is to write results to a temporary output file (like data_temp.txt), verify that the write succeeded, and then use Python's os.replace() to rename the temp file to the original name. This pattern ensures the original file is never at risk. This is the same strategy used by professional text editors and database systems.SUMMARY
Lesson Summary
Summary
In this lesson, you learned how to build Python programs that follow the input → process → output pattern using file I/O. You explored how to use the open() function with different file modes ("r", "w", "a"), how the with statement ensures files are properly closed, and how to read data line by line using .strip() and .split() to parse structured text.
Equally important, you practiced documenting assumptions—about file format, encoding, delimiters, data types, and file location—using comments and docstrings. You learned to wrap file operations in try/except blocks for error handling, and you saw how to use named constants for filenames to keep programs flexible and maintainable. These skills—reading input, producing output, and communicating your design decisions—form the foundation for every data-driven program you will write.