Reading and writing files is one of the first truly useful things you do in Python: saving results, loading configuration, processing logs, exporting data. Python's file handling is simple but has a few rules — modes, encodings, and proper closing — that separate fragile scripts from robust ones.

This guide covers file handling end-to-end: opening files with the right mode, reading whole files or line-by-line, writing and appending, the essential with statement, handling paths safely with pathlib, and working with CSV and JSON. Every example is runnable with its output.

Opening a File: open() and Modes

The built-in open(path, mode) returns a file object. The mode decides what you can do.

ModeMeaning
'r'Read (default); error if file is missing
'w'Write; creates or truncates the file
'a'Append; adds to the end, keeps existing content
'x'Create; error if the file already exists
'b' / 't'Binary / text (default), e.g. 'rb'
'r+'Read and write

Why You Should Always Use with

A file must be closed to flush data and free the OS handle. The with statement (a context manager) closes it automatically — even if an error occurs. Always prefer it over manual open()/close().

# The recommended pattern
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Hello, file!\n")
    f.write("Second line.\n")
# file is automatically closed here

print("done")

Output:

done

Reading Files

There are several ways to read, depending on whether you want the whole file or one line at a time.

with open("notes.txt", encoding="utf-8") as f:
    content = f.read()          # entire file as one string
print(content)

with open("notes.txt", encoding="utf-8") as f:
    lines = f.readlines()       # list of lines (keeps \n)
print(lines)

Output:

Hello, file!
Second line.

['Hello, file!\n', 'Second line.\n']

Reading line by line (memory-friendly)

Iterating the file object reads one line at a time — the right choice for large files.

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.strip())     # strip removes the trailing newline

Output:

Hello, file!
Second line.

Writing and Appending

'w' overwrites the whole file; 'a' adds to the end. Use writelines() to write a list of strings.

# Overwrite
with open("log.txt", "w", encoding="utf-8") as f:
    f.writelines(["line 1\n", "line 2\n"])

# Append
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("line 3 (appended)\n")

with open("log.txt", encoding="utf-8") as f:
    print(f.read())

Output:

line 1
line 2
line 3 (appended)

Working with Paths Using pathlib

The modern pathlib module handles file paths cleanly across operating systems and offers handy shortcuts.

from pathlib import Path

p = Path("data") / "report.txt"     # OS-correct path joining
p.parent.mkdir(parents=True, exist_ok=True)

p.write_text("quick save\n", encoding="utf-8")   # one-liner write
print(p.read_text(encoding="utf-8"))              # one-liner read
print("exists?", p.exists(), "| name:", p.name)

Output:

quick save

exists? True | name: report.txt

Real-World Example: CSV and JSON

Two of the most common file formats. Use the standard csv and json modules — never parse them by hand.

import csv, json

# Write and read CSV
with open("people.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "age"])
    writer.writerow(["Tushar", 25])

with open("people.csv", newline="", encoding="utf-8") as f:
    for row in csv.reader(f):
        print(row)

# Write and read JSON
data = {"name": "Tushar", "skills": ["python", "django"]}
with open("config.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2)

with open("config.json", encoding="utf-8") as f:
    print(json.load(f))

Output:

['name', 'age']
['Tushar', '25']
{'name': 'Tushar', 'skills': ['python', 'django']}

Common Mistakes to Avoid

  • Forgetting to close files — use with so it is automatic.
  • Opening with 'w' when you meant 'a''w' silently erases the file.
  • Ignoring encoding — always pass encoding="utf-8" for predictable text across platforms.
  • Reading a huge file with read() — iterate line by line instead.
  • Hard-coding path separators like "data/\\file" — use pathlib or os.path.join.
  • Not handling FileNotFoundError when a file may be missing.

Summary Table

TaskCode
Read allf.read()
Read lines (lazy)for line in f:
Write (overwrite)open(p, 'w')
Appendopen(p, 'a')
Safe handlingwith open(...) as f:
Quick read/writePath(p).read_text() / write_text()

Conclusion

File handling comes down to three habits: pick the correct mode, always use with, and specify the encoding. Add pathlib for clean cross-platform paths and the csv/json modules for structured data, and you can read, transform, and write almost any file safely.

Practice by writing a script that reads a text file, counts the words, and writes the result to a new file — using only with blocks.