Read and Write Text Files
Learn to manage files: read and write text files in Python. This hands-on tutorial covers opening, reading, writing, and closing text files, with troubleshooting and practical exercises.
Focus: manage files: read and write text files
Imagine writing a Python script that analyzes log files, generates a report, or saves user preferences, only to discover after an hour that the program crashed because the file you thought you wrote to disk is empty. This is the pain of implicit file management. Without a solid grasp on reading and writing text files, your data is ephemeral—it exists only while your script runs. Mastering file I/O is the gateway between your Python program and persistent storage, enabling you to save results, load configurations, and process external data with confidence.
The problem this lesson solves
Even simple scripts need to talk to the filesystem. You need to save a user's high score, read a list of cities from a CSV, or write a log of errors. Without proper file handling, you either lose data when the program ends or, worse, corrupt existing files by accident. The core problem is that Python has multiple ways to interact with files (reading, writing, appending), and beginners often miss crucial steps like closing files or handling encoding errors. This lesson removes that guesswork by giving you a repeatable, safe pattern for every text file operation.
Core concept / mental model
Think of a text file as a long piece of paper (the document) inside a folder. To work with it, you must:
1. Open the folder (open() function) — you get a pointer (file handle) marking your place.
2. Read or write on the paper (.read(), .write(), etc.) — the pointer moves as you work.
3. Close the folder (.close() or context manager) — formally ends the operation and makes sure every change is saved to disk.
A file path tells Python where the folder lives: either a full path like /home/user/data.txt (absolute) or a path relative to the script like 'data.txt' or '../logs/errors.txt'. In Python, the with statement (context manager) automates step 3, which is the recommended best practice because it ensures the file is closed even if an error occurs.
Pro tip: Always use a context manager (
with open(...) as f:) instead of plainopen()/.close(). It's less error-prone and makes your code more readable.
How it works step by step
Step 1: Choose the file mode
The open() function's second argument is the mode string:
| Mode | What it does | File pointer start at | Creates file if missing? | Overwrites content? |
|---|---|---|---|---|
'r' |
Read (default) | Beginning | No | No |
'w' |
Write (destructive) | Beginning | Yes | Yes |
'a' |
Append | End | Yes | No |
'x' |
Create exclusively | Beginning | Yes (but raises FileExistsError if exists) | No |
'r+' |
Read and write | Beginning | No | No (manual seek needed) |
Step 2: Open the file
Use open(filepath, mode) and a context manager:
with open('myfile.txt', 'r') as f:
content = f.read()
# file automatically closed here
Step 3: Perform the operation
For reading, you have several methods:
- .read() — reads entire content as a single string.
- .readline() — reads one line (including the newline character).
- .readlines() — returns a list of all lines (each with \n).
For writing:
- .write(string) — writes the string to the file.
- .writelines(list_of_strings) — writes each string in the list (no newlines added automatically).
Step 4: Handle encoding (always for text files)
Text files have an encoding (usually UTF-8). Always specify it to avoid UnicodeDecodeError:
with open('myfile.txt', 'r', encoding='utf-8') as f:
content = f.read()
Hands-on walkthrough
Example 1: Write data and then read it back
# Write some lines
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
with open('sample.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
# Read it back
with open('sample.txt', 'r', encoding='utf-8') as f:
contents = f.read()
print(contents)
Expected output:
Line 1
Line 2
Line 3
Pro tip:
.writelines()does not add newlines. You must include\nin each string you write.
Example 2: Append to an existing file
with open('sample.txt', 'a', encoding='utf-8') as f:
f.write("Appended line\n")
# Verify
with open('sample.txt', 'r', encoding='utf-8') as f:
print(f.read())
Expected output:
Line 1
Line 2
Line 3
Appended line
Example 3: Read a file line by line (memory-efficient for large files)
with open('sample.txt', 'r', encoding='utf-8') as f:
for line in f:
print(f"Read: {line.strip()}")
Expected output:
Read: Line 1
Read: Line 2
Read: Line 3
Read: Appended line
Compare options / when to choose what
The choice of mode depends on your goal:
| Scenario | Recommended mode | Why |
|---|---|---|
| Reading a config file | 'r' |
Safe, won't modify the data |
| Writing a new report | 'w' |
Clear old content first |
| Adding to a log file | 'a' |
Preserves past entries |
| Creating a file only if it doesn't exist | 'x' |
Prevents accidental overwrite |
| Updating a specific part of a file | 'r+' |
Manual .seek() needed |
When to read all at once vs line by line:
- Use .read() for small files (< 50 MB) or when you need the whole content at once (e.g., parsing a JSON file).
- Use iteration (for line in f:) for large files (e.g., processing million-line logs) to avoid memory explosion.
Troubleshooting & edge cases
Common Mistake 1: Forgetting to close the file without a context manager
f = open('data.txt', 'w')
f.write('hello')
# No close() — may not be written to disk!
Fix: Always use with open(...) as f:.
Common Mistake 2: FileNotFoundError when reading a non-existent file
with open('missing.txt', 'r') as f:
content = f.read()
# FileNotFoundError: [Errno 2] No such file or directory
Fix: Use 'w' or 'a' mode to create missing files, or check existence first:
import os
if os.path.exists('missing.txt'):
with open('missing.txt', 'r') as f:
content = f.read()
else:
print("File not found")
Common Mistake 3: UnicodeDecodeError / UnicodeEncodeError
# If file is not UTF-8
with open('data.bin', 'r', encoding='utf-8') as f:
content = f.read()
# UnicodeDecodeError
Fix: Specify the correct encoding (e.g., encoding='latin-1' for legacy files). When in doubt, read the file in binary mode 'rb' and then decode manually.
Common Mistake 4: Newline confusion with writelines
lines = ["line1", "line2"]
with open('output.txt', 'w') as f:
f.writelines(lines)
# Result: "line1line2" — no newlines!
Fix: Always include \n in each string or use f.write('\n'.join(lines)).
What you learned & what's next
You now know how to:
- Explain the difference between 'r', 'w', 'a', 'x', and 'r+' modes.
- Open and close files safely using the with statement.
- Read text files entirely or line by line.
- Write and append text to files.
- Troubleshoot common errors like FileNotFoundError and UnicodeDecodeError.
This foundation is critical because in the next lesson you'll learn to work with structured data like CSV and JSON files, where you'll combine file I/O with Python's parsing libraries. You'll never again lose data because of an unclosed file handle.
Practice recap
Create a Python script that asks the user to enter 3 lines of text, writes them to a file named user_notes.txt (one line each, with \n), and then reads the file back and prints each line with a line number. This exercise reinforces safe file writing, line-by-line reading, and proper use of encoding='utf-8'.
Common mistakes
- Forgetting to use a context manager (
with) — the file may not be written to disk if an exception occurs or if you forget to call.close(). - Using
'w'mode when you meant to append ('a') — this erases all existing content in the file without warning. - Ignoring encodings — reading a UTF-8 file without specifying
encoding='utf-8'can causeUnicodeDecodeErroron non-ASCII characters. - Assuming
.writelines()adds newlines — it does not; you must include\nin each string or join with newlines manually.
Variations
- Use
pathlib.Path.read_text()and.write_text()for a more modern, object-oriented API (Python 3.4+). - Use
shutil.copy()orfileinputfor batch processing or in-place modification of files. - Handle binary files with
'rb'and'wb'modes for images, audio, or any non-text data.
Real-world use cases
- Log file analyzers: Read server access logs line by line (hundreds of MB) to count unique IPs or detect patterns.
- Configuration backup tool: Write a script that reads a YAML config file, validates entries, and writes a backup copy with a timestamp.
- Data pipeline step: Write a CSV extractor that reads a text file of raw sensor data, cleans lines, and writes a clean CSV for downstream tools.
Key takeaways
- Always use
with open(...) as f:for automatic file closing. - Choose the correct mode (
'r','w','a','x','r+') based on whether you need to read, write, append, or all three. - Specify
encoding='utf-8'when working with text files to avoid encoding errors. - For large files, iterate line by line (
for line in f:) instead of loading the entire file into memory with.read(). .writelines()does not add newlines — you must explicitly include them in each string.- Handle
FileNotFoundErrorby checkingos.path.exists()or by using'w'/'a'modes that create missing files.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.