File Handling in Python

Reviewed & published by Brayan K

Learn to read, write, and manage files to save data permanently.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1. Why Learn File Handling?

When your program ends, all variables are lost. Files let you save data permanently so it survives after your program closes.

What Can You Do with Files?

2. Opening Files with open()

Use the open() function to work with files. You specify the filename and a mode that tells Python what you want to do.

ModeNameWhat It Does
"r"ReadOpen for reading (file must exist)
"w"WriteCreate new or overwrite existing
"a"AppendAdd to end (keeps existing content)
"x"CreateCreate new (error if file exists)

3. The with Statement (Best Practice)

Always use the with statement when working with files. It automatically closes the file when you are done, even if an error occurs.

file = open("data.txt", "r")
content = file.read()
file.close()  # Easy to forget!
with open("data.txt", "r") as file:
    content = file.read()
# Automatically closed!

4. Reading Files

There are several ways to read file content:

MethodWhat It ReturnsBest For
.read()Entire file as one stringSmall files
.readline()One line at a timeProcessing line by line
.readlines()List of all linesWhen you need a list
for line in file:Each line (loop)Large files (memory efficient)
# A string standing in for a file, so you can see the SHAPE of each result
# (the worked example further down does all of this with a real file)
file_content = """Line 1: Hello Python!
Line 2: File handling is useful.
Line 3: Keep practicing!"""

# Simulate .read() - entire file
print("=== read() - Entire file ===")
print(file_content)

# Simulate .readlines() - list of lines
print("\n=== readlines() - List of lines ===")
lines = file_content.split("\n")
print(lines)

# Simulate looping through lines
print("\n=== Loop through lines ===")
for i, line in enumerate(lines, 1):
    print(f"Line {i}: {line}")

# ✅ Expected output:
# === read() - Entire file ===
# Line 1: Hello Python!
# Line 2: File handling is useful.
# Line 3: Keep practicing!
#
# === readlines() - List of lines ===
# ['Line 1: Hello Python!', 'Line 2: File handling is useful.', 'Line 3: Keep practicing!']
#
# === Loop through lines ===
# Line 1: Line 1: Hello Python!
# Line 2: Line 2: File handling is useful.
# Line 3: Line 3: Keep practicing!

5. Writing to Files

Use mode "w" to write to a file. This creates a new file or overwrites an existing one.

# Simulating file writing
output = []

# Simulate writing lines
output.append("Hello, World!")
output.append("This is line 2.")
output.append("Python file handling is easy!")

print("=== File would contain ===")
for line in output:
    print(line)

# Real code would be:
# with open("output.txt", "w") as file:
#     file.write("Hello, World!\n")
#     file.write("This is line 2.\n")

# ✅ Expected output:
# === File would contain ===
# Hello, World!
# This is line 2.
# Python file handling is easy!

6. Appending to Files

Use mode "a" to add content to the end of a file without deleting existing content.

# Simulating append behavior
existing_content = [
    "Log entry 1: Started app",
    "Log entry 2: User logged in"
]

print("=== Existing content ===")
for line in existing_content:
    print(line)

# Append new entries
existing_content.append("Log entry 3: New action performed")
existing_content.append("Log entry 4: User logged out")

print("\n=== After appending ===")
for line in existing_content:
    print(line)

# ✅ Expected output:
# === Existing content ===
# Log entry 1: Started app
# Log entry 2: User logged in
#
# === After appending ===
# Log entry 1: Started app
# Log entry 2: User logged in
# Log entry 3: New action performed
# Log entry 4: User logged out
ModeExisting ContentUse Case
"w"Deleted (overwritten)Creating new files, saving fresh data
"a"Kept (added to)Logs, history, collecting data

7. Checking if a File Exists

Before reading a file, check if it exists to avoid errors:

import os

# Check if file exists
filename = "data.txt"

# Simulating os.path.exists behavior
files_on_disk = ["notes.txt", "config.json", "data.txt"]

if filename in files_on_disk:
    print(f"'{filename}' exists! Safe to open.")
else:
    print(f"'{filename}' not found.")

# Real code:
# if os.path.exists("data.txt"):
#     with open("data.txt", "r") as file:
#         content = file.read()
# else:
#     print("File not found")

# ✅ Expected output:
# 'data.txt' exists! Safe to open.

8. Handling File Errors

Use try-except to handle errors gracefully when working with files:

ErrorCause
FileNotFoundErrorFile does not exist (reading mode)
PermissionErrorNo permission to access file
IsADirectoryErrorTried to open a folder as a file
# Safe file reading with try-except

def read_file_safely(filename):
    try:
        # Simulate file operations
        if filename == "missing.txt":
            raise FileNotFoundError(f"No such file: '{filename}'")
        elif filename == "protected.txt":
            raise PermissionError(f"Access denied: '{filename}'")
        else:
            return f"Content of {filename}: Hello World!"
    except FileNotFoundError:
        return "Error: File not found."
    except PermissionError:
        return "Error: Permission denied."

# Test different scenarios
print(read_file_safely("data.txt"))
print(read_file_safely("missing.txt"))
print(read_file_safely("protected.txt"))

# ✅ Expected output:
# Content of data.txt: Hello World!
# Error: File not found.
# Error: Permission denied.

🟢 Worked Example: a real file, start to finish

The examples above stand in for files with strings and lists so you can see the shapes involved. This one uses a real file: it creates diary.txt, reads it back three different ways, appends to it, deliberately trips over a missing file, and deletes what it made. Nothing is pretend, and nothing is left behind.

# 🟢 WORKED EXAMPLE — create a real file, read it back, then clean up
import os

# ---------- 1. Write ----------
# "w" creates the file, or empties it if it already exists.
# The with block closes the file for you as soon as you leave it.
with open("diary.txt", "w") as file:
    file.write("Monday: started Python\n")     # write() adds NO line break — you do
    file.write("Tuesday: learned lists\n")

print("File created?", os.path.exists("diary.txt"))

# ---------- 2. Read the whole thing ----------
with open("diary.txt", "r") as file:
    everything = file.read()          # one long string, newlines included
print("=== read() gives one big string ===")
print(everything)

# ---------- 3. Read it as a list of lines ----------
with open("diary.txt") as file:       # no mode given, so "r" is assumed
    lines = file.readlines()
print("=== readlines() gives a list ===")
print(lines)                          # look closely: each item still ends in \n

# ---------- 4. Loop through it, one line at a time ----------
print("=== looping, tidied up with .strip() ===")
with open("diary.txt") as file:
    for number, line in enumerate(file, start=1):
        print(f"{number}. {line.strip()}")     # strip() removes the trailing newline

# ---------- 5. Append (mode "a" keeps what is already there) ----------
with open("diary.txt", "a") as file:
    file.write("Wednesday: read a file\n")

with open("diary.txt") as file:
    print("Lines now:", len(file.readlines()))

# ---------- 6. What a missing file actually looks like ----------
try:
    with open("missing.txt") as file:
        file.read()
except FileNotFoundError as err:
    print("FileNotFoundError:", err)

# ---------- 7. Tidy up ----------
os.remove("diary.txt")
print("Cleaned up. Still there?", os.path.exists("diary.txt"))

# ✅ Expected output:
# File created? True
# === read() gives one big string ===
# Monday: started Python
# Tuesday: learned lists
#
# === readlines() gives a list ===
# ['Monday: started Python\n', 'Tuesday: learned lists\n']
# === looping, tidied up with .strip() ===
# 1. Monday: started Python
# 2. Tuesday: learned lists
# Lines now: 3
# FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt'
# Cleaned up. Still there? False

Notice the blank line in the middle of the expected output. It is not a mistake: the file ends with a newline, and print() adds another one.

🎯 Your Turn: a scoreboard file

Three blanks, and the first two are the choice that trips everyone up at least once: which mode wipes the file, and which one adds to it.

# 🎯 YOUR TURN — fill in the blanks marked with ___
import os

# 1) Create scores.txt and put the first two results in it.
with open("scores.txt", "___") as file:    # 👉 the mode that creates a fresh file
    file.write("Alice 10\n")
    file.write("Bob 8\n")

# 2) A late result arrives. Add it WITHOUT wiping the first two.
with open("scores.txt", "___") as file:    # 👉 the mode that adds to the end
    file.write("Cara 12\n")

# Read the whole scoreboard back — already written for you.
with open("scores.txt") as file:
    lines = file.readlines()

print("Lines in file:", len(lines))
for line in lines:
    name, points = line.split()            # "Alice 10" -> ["Alice", "10"]
    print(f"{name} scored {points}")

# 3) Delete the file so a re-run starts clean.
os.___("scores.txt")                       # 👉 which os function deletes a file?
print("File still there?", os.path.exists("scores.txt"))

# ✅ Expected output:
# Lines in file: 3
# Alice scored 10
# Bob scored 8
# Cara scored 12
# File still there? False

If "Lines in file" comes out as 1, blank 2 is the problem: you opened the file in a mode that emptied it before writing, so Alice and Bob were thrown away.

9. Working with CSV Files

CSV (Comma-Separated Values) is a common format for spreadsheet data. Python has a built-in csv module to work with it.

import csv
from io import StringIO

# Create CSV data in memory
csv_data = StringIO()
writer = csv.writer(csv_data)
writer.writerow(["Name", "Age", "City"])
writer.writerow(["Alice", 25, "London"])
writer.writerow(["Bob", 30, "Paris"])
writer.writerow(["Charlie", 35, "Tokyo"])

# Read the CSV data
csv_data.seek(0)
print("=== CSV Content ===")
reader = csv.reader(csv_data)
for row in reader:
    print(row)

# Using DictReader for easier access
csv_data.seek(0)
print("\n=== As Dictionaries ===")
dict_reader = csv.DictReader(csv_data)
for row in dict_reader:
    print(f"{row['Name']} is {row['Age']} years old")

# ✅ Expected output:
# === CSV Content ===
# ['Name', 'Age', 'City']
# ['Alice', '25', 'London']
# ['Bob', '30', 'Paris']
# ['Charlie', '35', 'Tokyo']
#
# === As Dictionaries ===
# Alice is 25 years old
# Bob is 30 years old
# Charlie is 35 years old

10. Working with JSON Files

JSON (JavaScript Object Notation) is used for configuration files and APIs. Python can easily convert between JSON and dictionaries.

FunctionWhat It Does
json.dumps(obj)Convert Python dict → JSON string
json.loads(str)Convert JSON string → Python dict
json.dump(obj, file)Write dict to JSON file
json.load(file)Read JSON file to dict
import json

# Python dictionary
data = {
    "name": "Alice",
    "age": 25,
    "skills": ["Python", "JavaScript"],
    "active": True
}

# Convert to JSON string (for saving to file)
json_string = json.dumps(data, indent=2)
print("=== JSON String ===")
print(json_string)

# Convert back to Python dict (for reading from file)
parsed = json.loads(json_string)
print("\n=== Back to Python ===")
print(f"Name: {parsed['name']}")
print(f"Skills: {parsed['skills']}")

# ✅ Expected output:
# === JSON String ===
# {
#   "name": "Alice",
#   "age": 25,
#   "skills": [
#     "Python",
#     "JavaScript"
#   ],
#   "active": true
# }
#
# === Back to Python ===
# Name: Alice
# Skills: ['Python', 'JavaScript']

🏁 Mini-Challenge: save settings as real JSON

No blanks — a brief and an outline. Save a settings dictionary to a real settings.json file, print the raw text that ended up in it, load it straight back, and prove that what came back is identical to what went in.

Remember which function is which: json.dump(data, file) writes to a file, json.dumps(data) returns a string. The plural s is for "string". Same pair on the way back: json.load(file) versus json.loads(text).

# 🏁 MINI-CHALLENGE — write it yourself
import json
import os

settings = {"theme": "dark", "font_size": 14, "autosave": True}

# 1. Open settings.json for writing and json.dump(settings, file, indent=2)
#    indent=2 makes the file readable by a human instead of one long line.
# 2. Open it again and print(file.read()) so you can see the raw text.
# 3. Open it once more and json.load(file) into a variable called loaded.
# 4. Print whether loaded == settings, then print loaded["theme"],
#    then print type(loaded["autosave"]).__name__
# 5. os.remove the file and print whether it is gone.

# your code here


# ✅ Expected output:
# {
#   "theme": "dark",
#   "font_size": 14,
#   "autosave": true
# }
#
# Same as the original? True
# Theme: dark
# Type of autosave: bool
# Cleaned up: True
#
# Look at the file text: Python's True was written as JSON's lowercase true.
# JSON is its own format with its own spelling — but it comes back as a real
# Python bool, which is what step 4 proves.

11. Common Mistakes to Avoid

MistakeProblemSolution
Forgetting to close fileData may not save, file lockedUse with statement
Using "w" instead of "a"Existing data deletedDouble-check your mode
Forgetting \nAll text on one lineAdd \n after each line
Reading non-existent fileFileNotFoundErrorCheck with os.path.exists()
Wrong encodingStrange characters appearAdd encoding="utf-8"

12. Practical Examples

Example 1: Simple Note Saver

# Simple note saver simulation
notes = []

def save_note(note):
    notes.append(note)
    print(f"Saved: {note}")

def show_notes():
    print("\n=== Your Notes ===")
    for i, note in enumerate(notes, 1):
        print(f"{i}. {note}")

# Add some notes
save_note("Buy groceries")
save_note("Call mom")
save_note("Finish Python lesson")

# Display all notes
show_notes()

# Real implementation:
# with open("notes.txt", "a") as f:
#     f.write(note + "\n")

# ✅ Expected output:
# Saved: Buy groceries
# Saved: Call mom
# Saved: Finish Python lesson
#
# === Your Notes ===
# 1. Buy groceries
# 2. Call mom
# 3. Finish Python lesson

Example 2: Word Counter

# Count words, lines, and characters in text

text = """Python is a great programming language.
It is easy to learn and very powerful.
Many developers love using Python for various projects."""

lines = text.split("\n")
words = text.split()
chars = len(text)

print("=== Text Analysis ===")
print(f"Lines: {len(lines)}")
print(f"Words: {len(words)}")
print(f"Characters: {chars}")

# Count word frequency
word_count = {}
for word in words:
    word = word.lower().strip(".,!?")
    word_count[word] = word_count.get(word, 0) + 1

print("\n=== Word Frequency ===")
for word, count in sorted(word_count.items()):
    if count > 1:
        print(f"  '{word}': {count} times")

# ✅ Expected output:
# === Text Analysis ===
# Lines: 3
# Words: 22
# Characters: 134
#
# === Word Frequency ===
#   'is': 2 times
#   'python': 2 times

Example 3: Simple Log System

from datetime import datetime

# Simple logging system
log = []

def log_event(event):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entry = f"[{timestamp}] {event}"
    log.append(entry)
    print(entry)

# Log some events
log_event("Application started")
log_event("User logged in")
log_event("File saved successfully")
log_event("User logged out")

print("\n=== Complete Log ===")
for entry in log:
    print(entry)

Summary: Quick Reference

OperationCode
Read entire filewith open("f.txt", "r") as f: text = f.read()
Read linesfor line in file:
Write (overwrite)with open("f.txt", "w") as f: f.write("text")
Appendwith open("f.txt", "a") as f: f.write("more")
Check existsos.path.exists("f.txt")
Read CSVcsv.reader(file)
Read JSONjson.load(file)
Write JSONjson.dump(data, file)

Lesson complete — your programs can now save and load data!

Reading, writing, appending, CSV, JSON — you know how to work with files safely using the with statement. Your programs can now remember things between runs.

Practice quiz

Which file mode opens a file for reading (and errors if it doesn't exist)?

  • "w"
  • "a"
  • "r"
  • "x"

Answer: "r". Mode "r" opens for reading; the file must already exist.

What does opening a file in mode 'w' do to existing content?

  • Appends to the end
  • Overwrites (deletes) it
  • Raises an error
  • Leaves it unchanged

Answer: Overwrites (deletes) it. Mode "w" creates a new file or overwrites an existing one, deleting its content.

Which mode adds to the end of a file without deleting existing content?

  • "a"
  • "w"
  • "r"
  • "x"

Answer: "a". Mode "a" (append) keeps existing content and adds to the end.

Why is 'with open(...) as file:' the recommended way to handle files?

  • It runs faster
  • It automatically closes the file, even if an error occurs
  • It allows multiple modes at once
  • It compresses the file

Answer: It automatically closes the file, even if an error occurs. The with statement auto-closes the file when the block ends, even on errors.

What does the .read() method return?

  • A list of lines
  • One line at a time
  • The entire file as one string
  • The number of characters

Answer: The entire file as one string. .read() returns the whole file content as a single string.

Which method returns a list of all lines in a file?

  • .read()
  • .readline()
  • .readlines()
  • .lines()

Answer: .readlines(). .readlines() returns a list where each element is one line.

Does .write() automatically add a newline at the end of each call?

  • Yes, always
  • No, you must add yourself
  • Only in append mode
  • Only for the first line

Answer: No, you must add yourself. .write() does not add line breaks; you add yourself for separate lines.

Which exception is raised when reading a file that doesn't exist?

  • FileNotFoundError
  • PermissionError
  • ValueError
  • KeyError

Answer: FileNotFoundError. Reading a missing file raises FileNotFoundError.

Which function checks whether a file exists before opening it?

  • os.exists()
  • os.path.exists()
  • file.exists()
  • os.check()

Answer: os.path.exists(). os.path.exists('file.txt') returns True if the path exists.

Which json function reads a JSON file directly into a Python object?

  • json.loads(str)
  • json.dumps(obj)
  • json.load(file)
  • json.dump(obj, file)

Answer: json.load(file). json.load(file) reads from an open file object into Python; loads is for strings.

Continue this course