SythraOpen app

File Handling in Python (Reading, Writing, CSV & JSON)

File handling in Python uses open(filename, mode) to read ('r'), write ('w', which erases existing content), or append ('a') to a file — always wrapped in a with statement so the file closes automatically, even if an error occurs.

Sythra

13 min read

XLinkedIn
File Handling in Python (Reading, Writing, CSV & JSON) — cover illustration

Everything you've built so far has lived and died inside the program's memory. The moment your program ends, every variable, every list, every dictionary — all of it disappears completely, like a sandcastle washed away by the tide. If you want your program to actually remember something after it finishes running — a saved game, a list of names, a log of what happened — you need to store it somewhere that survives. That somewhere is a file.

Think of a file like a notebook sitting in a drawer. Your program can open the drawer, read what's written, write new things into it, and close the drawer again. The notebook stays exactly where it is, even after you walk away.

What you will learn

  • How to open files in read, write, and append modes — and why 'w' is dangerous
  • Four ways to read a file, and why looping directly over it is usually best
  • Why with is the professional, always-recommended way to handle files
  • Reading and writing CSV and JSON — the two most common structured formats
  • The most common file-handling bugs, and how to avoid every one of them

Opening a file

To work with a file, you first open it with Python's built-in open() function:

f = open('notes.txt', 'r')

open() takes a filename and a mode, telling Python what you intend to do with the file:

'r'   # Read — the file must already exist, or Python raises an error
'w'   # Write — creates a new file, or completely WIPES an existing one clean first
'a'   # Append — adds new content to the end, without erasing what's already there
'x'   # Create — makes a brand-new file, but fails if it already exists

Reading a file

.read() slurps the entire file into a single string, all at once. Great for small files:

f = open('notes.txt', 'r')
contents = f.read()
print(contents)
f.close()

.readline() reads exactly one line at a time, remembering where it left off, returning an empty string once nothing is left. .readlines() reads the whole file and returns a list of strings, one per line, each still carrying its trailing \n:

f = open('notes.txt', 'r')
lines = f.readlines()
print(lines)
# → ['First line\n', 'Second line\n', 'Third line\n']
f.close()

The genuinely best way to read a file line by line — loop directly over the file object itself, exactly like looping over a list:

f = open('notes.txt', 'r')
for line in f:
    print(line.strip())   # .strip() removes the trailing newline and extra whitespace
f.close()

This is preferred over .readlines() for big files, since it processes one line at a time, rather than loading the entire file into memory upfront.

Writing to a file

f = open('notes.txt', 'w')
f.write('Hello, this is my first line.\n')
f.write('Here is a second line.\n')
f.close()

.write() puts exactly the text you give it into the file — it does not automatically add a newline the way print() does. If you want each .write() to land on its own line, include \n yourself.

To add new content without wiping what's already there, use 'a' mode instead of 'w'. And .writelines() writes a whole list of strings, one after another, without adding newlines for you.

Always close your files — or better, use with

Every call to open() uses up a resource, and Python expects you to .close() it when you're done. Forget to close a file, and your changes might not actually save to disk, other programs might be locked out of it, and if your program opens many files without closing them, you can eventually run out of file handles and crash.

The genuinely important habit: use a with statement instead of manually calling .close():

with open('notes.txt', 'r') as f:
    contents = f.read()
    print(contents)

# f is automatically, guaranteed-ly closed right here, even if something went wrong above!

with open(...) as f: opens the file, hands it to you as f, and automatically closes it the moment you leave the indented block — even if an exception crashes your code halfway through. This pattern is called a context manager, and it's the standard, professional, always-recommended way to work with files in Python.

# Reading
with open('notes.txt', 'r') as f:
    for line in f:
        print(line.strip())

# Writing
with open('output.txt', 'w') as f:
    f.write('Saved successfully!\n')

Handling files that don't exist

If you open a file for reading and it doesn't exist, Python raises FileNotFoundError — the exception you met in Try/Except and Exception Handling. Wrap your file-opening code accordingly:

try:
    with open('does_not_exist.txt', 'r') as f:
        contents = f.read()
        print(contents)
except FileNotFoundError:
    print("Sorry, that file doesn't exist. Please check the filename.")

This is dramatically better than letting your whole program crash because a user mistyped a filename.

Working with file paths

A relative path ('data/notes.txt') describes where a file is, relative to wherever your program is currently running. An absolute path gives the file's complete address:

f = open('/Users/asha/Documents/notes.txt', 'r')      # Mac/Linux style
f = open('C:\\Users\\Asha\\Documents\\notes.txt', 'r')  # Windows style

Python's os.path module builds correct paths automatically for whatever operating system you're on:

import os

full_path = os.path.join('data', 'notes.txt')
print(full_path)
# → 'data/notes.txt'   (or 'data\\notes.txt' on Windows — handled automatically!)

if os.path.exists('notes.txt'):
    print("The file exists!")

CSV files: structured data, row by row

A huge amount of real-world data lives in CSV ("Comma-Separated Values") files — plain text, one row per line, commas separating columns. Python's csv module handles tricky edge cases (like commas inside a quoted value) for you:

import csv

with open('people.csv', 'r') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

# ['name', 'age', 'city']
# ['Asha', '25', 'Mumbai']
# ['Ravi', '31', 'Delhi']

Each row is a plain list of strings, with the first row usually the header. next(reader) pulls exactly one row out manually, so a following loop starts from wherever it left off. Even more convenient — csv.DictReader automatically uses the header row to build a dictionary per row:

with open('people.csv', 'r') as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row['name'], 'is', row['age'], 'years old')

Now you access each value by column name, instead of remembering "age is the second column."

import csv

data = [
    ['name', 'age', 'city'],
    ['Mei', '28', 'Tokyo'],
    ['Tom', '22', 'London'],
]

with open('output.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerows(data)

A quick preview: JSON files

JSON (JavaScript Object Notation) looks almost exactly like a Python dictionary, and it's one of the most common ways data gets saved to files and sent across the internet:

import json

data = {'name': 'Asha', 'age': 25, 'city': 'Mumbai'}

# Writing a dictionary out to a JSON file
with open('person.json', 'w') as f:
    json.dump(data, f)

# Reading it back in
with open('person.json', 'r') as f:
    loaded_data = json.load(f)

print(loaded_data)
# → {'name': 'Asha', 'age': 25, 'city': 'Mumbai'}

json.dump(data, f) writes a dictionary (or list) into a file as JSON text; json.load(f) reads it back and rebuilds it into a real Python dictionary — almost like it went to sleep inside a file and woke back up unchanged. This is one of the most common ways real programs save data between runs, especially once your data involves nested structures.

Binary mode: for non-text files

Everything so far has been text mode. Some files — images, audio, compiled programs — aren't text at all. For those, add a b to your mode string:

with open('photo.jpg', 'rb') as f:    # 'rb' = read binary
    data = f.read()
    print(type(data))
    # → <class 'bytes'>   (not a string!)

In binary mode, .read() hands you back a bytes object instead of a str — raw, untranslated data, exactly as stored on disk.

Debugging: common file handling traps

  • Forgetting which mode wipes the file — 'w' erases everything before you write a single byte. Always pause and ask: do I want to read, overwrite, or append?
  • Forgetting .close() — solved permanently by always using with instead of manual open()/close() pairs.
  • Reading from a file opened in write mode (or vice versa) — a file opened 'w' is write-only; calling .read() on it crashes with io.UnsupportedOperation.
  • Not stripping newlines — every line read from a file (except possibly the last) carries an invisible \n. 'done' and 'done\n' are not the same string, so always .strip() before comparing.
  • Relative paths behaving unexpectedly — if your program runs from a different folder than you expect, a relative path might point somewhere else entirely. Print os.getcwd() to check where Python thinks "here" is.

Common questions

What does 'w' mode do to an existing file in Python?

Opening a file with 'w' mode immediately erases all of its existing content, even before you write anything new. If you want to add content without erasing what's already there, use 'a' (append) mode instead.

Why should you use a with statement to open files in Python?

with open(...) as f: automatically closes the file when the indented block ends, even if an exception occurs inside it. This guarantees the file is properly closed and avoids resource leaks or unsaved data, unlike manually calling .close() which can be skipped if an error occurs first.

What is the best way to read a large file line by line in Python?

Loop directly over the file object: for line in f:. This reads and processes one line at a time instead of loading the entire file into memory at once, which is what .read() and .readlines() do.

How do you read a CSV file in Python?

Use the csv module: csv.reader(f) yields each row as a list of strings, while csv.DictReader(f) uses the first row as column headers and yields each row as a dictionary, letting you access values by column name.

What is the difference between JSON and CSV files?

CSV stores flat, tabular data as comma-separated rows, best for simple spreadsheet-like data. JSON can represent nested structures like dictionaries containing lists containing more dictionaries, making it better suited for more complex data saved with json.dump() and restored with json.load().

Explore

Related topics

Keep going — these sit next to this concept in a real learning path.

Browse all python explainers →