March 10, 2026 · All About CS
The Complete Guide to File Handling in Python
Master every file operation in Python — creating, writing, reading, appending, and deleting files. Includes the with statement, file pointers, the os module, and practical patterns for safe file I/O.
The Complete Guide to File Handling in Python
Files are how programs remember things after they stop running. Every variable, every computed result, every piece of user input — it all vanishes from memory the moment your script finishes. File handling gives your programs a permanent memory: a way to store data on disk and retrieve it later.
This guide covers the full lifecycle — creating, writing, reading, appending, and deleting files — along with professional patterns like the with statement and defensive existence checks using the os module.
Why File Handling Matters
Think about a scenario: you write a program, run it, enter some data, perform operations, and get output. When the program stops, everything is gone — cleared from memory because it was all temporary.
But what if those values are important? What if you want to pick up where you left off next time you run the program?
You could use a database, but that adds complexity: schema design, connection management, query languages. For many use cases — logging, configuration, caching, simple data persistence — a text file is the simplest and most pragmatic solution.
When to use files vs. databases: Files are ideal for configuration, logging, small datasets, and data exchange between programs. Databases become necessary when you need structured queries, concurrent access, or datasets that grow beyond what fits comfortably in a single file.
The open() Function — Your Gateway to File Operations
Every file operation in Python starts with the built-in open() function. It takes two arguments: the file path and the mode that describes what you intend to do with the file.
my_file = open("path/to/file.txt", "mode")
# ... perform read or write operations ...
my_file.close()After every operation, you must close the file with .close(). Leaving files open can lock them, prevent other programs from accessing them, and cause data corruption.
File Modes Explained
The mode string tells Python exactly how you want to interact with the file:
| Mode | Name | Description |
|---|---|---|
"r" | Read | Open an existing file for reading. Raises an error if the file doesn't exist. |
"w" | Write | Open a file for writing. Creates the file if it doesn't exist. Overwrites existing content. |
"a" | Append | Open a file for appending. Creates the file if it doesn't exist. Preserves existing content. |
"x" | Create | Create a new file. Raises an error if the file already exists. |
"r+" | Read + Write | Open for both reading and writing. Raises an error if the file doesn't exist. |
"w+" | Write + Read | Open for writing and reading. Creates the file if it doesn't exist. Overwrites existing content. |
"a+" | Append + Read | Open for appending and reading. Creates the file if it doesn't exist. File pointer starts at the end. |
The r+ vs w+ distinction: Both allow reading and writing, but r+ requires the file to already exist (raises FileNotFoundError otherwise), while w+ creates the file if missing — and erases all existing content in the process.
Creating and Writing Files
The most common way to create a file and write data into it is using "w" mode:
my_file = open("stored_files/program_data.txt", "w")
my_file.write("Hello There! Program data stored successfully!")
my_file.close()After running this code, a file named program_data.txt appears inside the stored_files/ directory with the written text inside it. If the file already existed, its previous contents are completely replaced.
Writing Multiple Lines
Each call to .write() continues from where the previous one stopped — on the same line. To write on separate lines, insert the newline character \n:
my_file = open("stored_files/program_data.txt", "w")
my_file.write("Line 1: Data stored successfully!\n")
my_file.write("Line 2: More data follows.\n")
my_file.write("Line 3: Final entry.")
my_file.close()The resulting file:
Line 1: Data stored successfully!
Line 2: More data follows.
Line 3: Final entry.Reading Files
To read an existing file, open it in "r" mode:
my_file = open("stored_files/program_data.txt", "r")
content = my_file.read()
print(content)
my_file.close()The .read() method returns the entire content of the file as a single string.
Reading Line by Line
For large files, reading everything at once isn't practical. The .readline() method reads one line at a time:
my_file = open("stored_files/program_data.txt", "r")
first_line = my_file.readline()
second_line = my_file.readline()
print("Line 1:", first_line)
print("Line 2:", second_line)
my_file.close()Each call to .readline() advances the internal file pointer to the next line. The first call returns line 1, the second call returns line 2, and so on.
Reading All Lines Into a List
The .readlines() method returns every line as an element in a list — useful when you want to process lines with a loop:
my_file = open("stored_files/program_data.txt", "r")
lines = my_file.readlines()
for i, line in enumerate(lines, 1):
print(f"Line {i}: {line.strip()}")
my_file.close()Appending to Files
Append mode ("a") adds new content at the end of the file without touching existing data:
my_file = open("stored_files/program_data.txt", "a")
my_file.write("\nAppended: Here goes some more data!")
my_file.close()Write vs. Append — The Critical Difference
This distinction trips up every beginner at least once:
| Mode | Existing Content | File Pointer Starts At |
|---|---|---|
"w" | Erased — completely overwritten | Beginning of file |
"a" | Preserved — new data added after it | End of file |
Run a program with "w" mode ten times, and you'll only see the output from the last run. Run it with "a" mode ten times, and you'll see all ten outputs stacked.
# Demonstrating append behavior
for i in range(3):
my_file = open("stored_files/counter.txt", "a")
my_file.write(f"Run #{i + 1}\n")
my_file.close()
# counter.txt now contains:
# Run #1
# Run #2
# Run #3Deleting Files
Python's built-in open() function handles creation, reading, and writing — but not deletion. For that, you need the os module:
import os
os.remove("stored_files/program_data.txt")After this runs, the file is permanently deleted from disk.
Defensive Deletion — Avoiding FileNotFoundError
If the file doesn't exist, os.remove() raises a FileNotFoundError. Always check first:
import os
file_path = "stored_files/program_data.txt"
if os.path.exists(file_path):
os.remove(file_path)
print(f"Deleted: {file_path}")
else:
print(f"File not found: {file_path}")This pattern applies to reading too. Before opening a file in "r" mode, use os.path.exists() to verify it's actually there. This prevents your program from crashing on missing files.
The with Statement — The Professional Way
Every example so far has manually called .close(). Forget that line once, and you risk data corruption or locked files. The with statement eliminates this risk entirely:
with open("stored_files/program_data.txt", "w") as my_file:
my_file.write("Line 1: Written safely.\n")
my_file.write("Line 2: No need to call close().\n")
# File is automatically closed here, even if an error occurs inside the blockThe with statement is a context manager — it guarantees the file is closed when the block ends, regardless of whether the code inside succeeds or raises an exception. This is the recommended pattern for all file operations in professional Python code.
Rewriting All Operations With with
Writing:
with open("data.txt", "w") as f:
f.write("Fresh content.\n")Reading:
with open("data.txt", "r") as f:
content = f.read()
print(content)Appending:
with open("data.txt", "a") as f:
f.write("Appended safely.\n")Understanding the File Pointer
The file pointer (also called the file handle) is an internal cursor that determines where the next read or write operation starts.
| Mode | Pointer Starts At | Behavior |
|---|---|---|
"r" | Beginning | Reads forward from start |
"w" | Beginning | Erases file, writes from start |
"a" | End | Writes after existing content |
"r+" | Beginning | Reads and writes from start |
"a+" | End | Can read and write, but pointer starts at end |
This is why append mode preserves data and write mode overwrites it — the pointer starts in different positions, and write mode clears the file first.
When you call .readline(), the pointer advances to the next line. Call it again, and you get the next line — not the first one again. The pointer always moves forward.
with open("data.txt", "r") as f:
print(f.readline()) # → Line 1 (pointer moves to line 2)
print(f.readline()) # → Line 2 (pointer moves to line 3)
print(f.tell()) # → Current byte position of the pointerYou can also manually reset the pointer with .seek():
with open("data.txt", "r") as f:
content = f.read() # Reads entire file, pointer at end
f.seek(0) # Reset pointer to the beginning
first_line = f.readline() # Now reads from the start againPractical Pattern: Safe Read-or-Create
A common real-world pattern combines existence checking with file creation:
import os
FILE_PATH = "config/settings.txt"
if os.path.exists(FILE_PATH):
with open(FILE_PATH, "r") as f:
settings = f.read()
print("Loaded settings:", settings)
else:
default_settings = "theme=dark\nlanguage=en\n"
os.makedirs(os.path.dirname(FILE_PATH), exist_ok=True)
with open(FILE_PATH, "w") as f:
f.write(default_settings)
print("Created default settings file.")File Operation Cheat Sheet
# Create and write (overwrites existing content)
with open("file.txt", "w") as f:
f.write("content")
# Read entire file
with open("file.txt", "r") as f:
data = f.read()
# Read line by line
with open("file.txt", "r") as f:
for line in f:
print(line.strip())
# Append to existing file
with open("file.txt", "a") as f:
f.write("new content\n")
# Check if file exists
import os
os.path.exists("file.txt") # → True or False
# Delete a file
os.remove("file.txt")
# Create directories if needed
os.makedirs("path/to/dir", exist_ok=True)What's Next?
Now that you can persist data to files, the next logical step is learning how to handle things when they go wrong. In the next tutorial, we'll explore error handling in Python — using try, except, finally, and custom exceptions to write robust programs that fail gracefully instead of crashing.
Happy coding. 🐍