New: 6 free SQL practice datasets with 300+ questions — try the SQL Compiler →
Python

Reading and Writing Files in Python

Read and write files in Python: open, modes r w a, read vs readline, encodings, pathlib, CSV and JSON, with real code, output and common mistakes.

Upskly AI Team September 26, 2026 10 min read
Reading and Writing Files in Python

To read and write files in Python, use open(path, mode) inside a with block. Mode "r" reads, "w" writes (and erases whatever was there), and "a" appends to the end. Inside the block, f.read() gets the whole text, f.readline() one line, and f.write("text") puts text into the file. The with block closes the file for you.

This guide covers the modes, three ways to read, appending, encodings, the friendlier pathlib module, and quick recipes for CSV and JSON, with real output for every example.

In this guide

The short version

  • with open("file.txt", "w") as f: then f.write(text) to write.
  • with open("file.txt") as f: then f.read() or for line in f: to read.
  • Modes: r read (default), w write and erase, a append, x create only.
  • Always use with so the file is closed, and give an encoding for text you share.

Writing and reading a text file

The pattern is always the same: open the file, use it, and let the with block close it (see Python Context Managers and the with Statement). Here we write two lines and read them back:

with open("notes.txt", "w") as f:
    f.write("first line\n")
    f.write("second line\n")

with open("notes.txt", "r") as f:
    text = f.read()
print(text)
print(len(text))

Output

first line
second line

23

write() does not add a newline for you, so we put \n at the end of each line ourselves. The text we read back is 23 characters: 11 for the first line, 12 for the second, and the printed blank line is the final newline we wrote.

File modes: r, w, a and friends

The open() modes
ModeMeaningIf the file does not existExisting content
rRead (the default)ErrorKept
wWriteCreatedErased first
aAppendCreatedKept, new text goes at the end
xCreate onlyCreatedError if the file already exists
r+Read and writeErrorKept
w+Write and readCreatedErased first
b (added, as in rb)Binary mode (bytes, not text)

The one to be careful with is "w": it wipes the file the moment you open it. Mode "x" is the safe way to say “create a new file, and fail rather than overwrite”:

with open("once.txt", "x") as f:
    f.write("hi")
try:
    open("once.txt", "x")
except FileExistsError as e:
    print(type(e).__name__)

Output

FileExistsError

Three ways to read: read, readline, readlines

Each method gives you a different amount of the file, and they share one position that moves forward as you read:

with open("notes.txt", "w") as f:
    f.write("a\nb\nc\n")

with open("notes.txt") as f:
    print(repr(f.readline()))
    print(repr(f.read()))
with open("notes.txt") as f:
    print(f.readlines())
with open("notes.txt") as f:
    for line in f:
        print(line.strip())

Output

'a\n'
'b\nc\n'
['a\n', 'b\n', 'c\n']
a
b
c
  • read() – everything from the current position, as one string.
  • readline() – just the next line, including its \n.
  • readlines() – all the lines as a list of strings.

After readline() took the first line, read() returned only what was left, which is the position moving forward.

Reading line by line (the best way for big files)

The last example above looped over the file directly: for line in f:. This is the best habit. It reads one line at a time, so even a file of several gigabytes uses almost no memory, unlike read() or readlines(), which pull the entire file into memory. The file object is an iterator (see Python Iterators and Generators Explained).

A typical job: parse a small data file into a dictionary.

with open("scores.txt", "w") as f:
    f.write("Asha,90\nRavi,72\nMeera,85\n")

scores = {}
with open("scores.txt") as f:
    for line in f:
        name, mark = line.strip().split(",")
        scores[name] = int(mark)
print(scores)
print(round(sum(scores.values()) / len(scores), 1))

Output

{'Asha': 90, 'Ravi': 72, 'Meera': 85}
82.3

strip() removes the trailing newline, split(",") breaks the line into fields, and int() converts the text of the mark into a number. Files always give you text, so conversion is your job.

Appending vs overwriting

Compare "a" and "w" on the same file:

with open("log.txt", "w") as f:
    f.write("one\n")
with open("log.txt", "a") as f:
    f.write("two\n")
with open("log.txt") as f:
    print(f.read().split())
with open("log.txt", "w") as f:
    f.write("three\n")
with open("log.txt") as f:
    print(f.read().split())

Output

['one', 'two']
['three']

Append added “two” after “one”. Opening with "w" then threw everything away and left only “three”. Use "a" for logs and anything that should keep growing.

Encoding: characters vs bytes

A file stores bytes. Text has to be encoded into bytes to be saved and decoded back to be read, and both sides must agree on the encoding. Use encoding="utf-8" explicitly for any text that might contain non-English characters, because the default depends on the computer:

with open("hi.txt", "w", encoding="utf-8") as f:
    f.write("नमस्ते")
with open("hi.txt", encoding="utf-8") as f:
    print(len(f.read()))
with open("hi.txt", "rb") as f:
    print(len(f.read()))

Output

6
18

The Hindi word has 6 characters, but takes 18 bytes in UTF-8, since each of these characters needs 3. The "rb" mode reads raw bytes, with no decoding.

pathlib: a nicer way to handle paths

The pathlib module treats paths as objects. The / operator joins parts (and uses the right separator for your system), and there are one-line helpers for reading and writing small files:

from pathlib import Path

p = Path("data") / "report.txt"
p.parent.mkdir(exist_ok=True)
p.write_text("total=42")
print(p.read_text())
print(p.name, p.suffix, p.stem)
print(p.exists(), Path("nope.txt").exists())
print(sorted(x.name for x in Path("data").iterdir()))

Output

total=42
report.txt .txt report
True False
['report.txt']

Prefer it to gluing strings together. It also avoids the classic Windows trap of backslashes in paths, which are escape characters in normal strings:

print("C:\new")
print(r"C:\new")

Output

C:
ew
C:\new

The first line lost its backslash: \n in "C:\new" is a newline. Write a raw string (r"..."), use forward slashes, or use pathlib.

CSV and JSON in a few lines

For structured data you rarely need to parse text yourself. The standard library has the csv and json modules. JSON keeps nested data (lists, dictionaries) intact:

import json

data = {"name": "Asha", "skills": ["sql", "python"]}
with open("profile.json", "w") as f:
    json.dump(data, f)
with open("profile.json") as f:
    loaded = json.load(f)
print(loaded == data, loaded["skills"])

Output

True ['sql', 'python']

And CSV handles commas, quotes and line endings for you. Open CSV files with newline="", as the module’s documentation recommends. Note that everything read back from a CSV is a string:

import csv

rows = [["name", "marks"], ["Asha", 90], ["Ravi", 72]]
with open("marks.csv", "w", newline="") as f:
    csv.writer(f).writerows(rows)
with open("marks.csv", newline="") as f:
    for row in csv.reader(f):
        print(row)

Output

['name', 'marks']
['Asha', '90']
['Ravi', '72']

For anything bigger, such as analysing a spreadsheet, the pandas library is the usual next step.

When the file is missing

Opening a file that does not exist in read mode raises FileNotFoundError. Catch it if a missing file is a normal situation (see Python Exception Handling):

try:
    open("missing.txt")
except FileNotFoundError as e:
    print(type(e).__name__)
    print(e.filename)

Output

FileNotFoundError
missing.txt

Common mistakes

Mistake 1: opening with “w” and losing the data

Mode "w" erases the file immediately, before you write anything. Use "a" to keep existing content, or "x" to refuse to overwrite.

Mistake 2: writing something that is not a string

write() accepts only text. Convert numbers first:

with open("n.txt", "w") as f:
    try:
        f.write(5)
    except TypeError as e:
        print(type(e).__name__)
    f.write(str(5))
with open("n.txt") as f:
    print(f.read())

Output

TypeError
5

Mistake 3: reading after writing without moving back

The file position sits at the end after a write, so a read returns nothing until you seek(0) back to the start:

with open("p.txt", "w+") as f:
    f.write("hello")
    print(repr(f.read()))
    f.seek(0)
    print(repr(f.read()))

Output

''
'hello'

Mistake 4: not using with

A file you forget to close can lose buffered data or stay locked. The with statement removes the risk.

Mistake 5: reading a huge file with read()

Loop over the file, line by line, instead.

Try it yourself

Work out each answer first, then open the solution.

1. Write the three lines a, b, c to a file and count them by reading the file back.

Show solution
with open("t.txt", "w") as f:
    f.write("a\nb\nc\n")
with open("t.txt") as f:
    print(len(f.readlines()))

Output

3

2. What happens if you open a file that does not exist in mode "a" and write "x"?

Show answer
with open("new.txt", "a") as f:
    f.write("x")
with open("new.txt") as f:
    print(f.read())

Output

x

Append mode creates the file if it is missing, so this works.

3. A file has one number per line. Print their total.

Show solution
with open("nums.txt", "w") as f:
    f.write("10\n20\n30\n")
with open("nums.txt") as f:
    print(sum(int(line) for line in f))

Output

60

The generator expression converts each line with int(). The newline at the end of each line does not matter, because int() ignores surrounding whitespace.

4. What are the .suffix and .name of the path a/b/c.tar.gz?

Show answer
from pathlib import PurePosixPath

p = PurePosixPath("a/b/c.tar.gz")
print(p.suffix, p.name)

Output

.gz c.tar.gz

The suffix is only the last extension. The .name is the whole final part.

Try the ideas in our free Python compiler.

Frequently asked questions

How do I read a file in Python?

Use with open("file.txt") as f: and then f.read() for the whole text, or for line in f: to go through it one line at a time.

What is the difference between w and a mode?

w erases the existing file content before writing. a keeps the content and adds new text at the end.

Why should I use with when opening a file?

The with statement closes the file automatically when the block ends, even if an error occurs, so you cannot forget to close it.

How do I read a large file without running out of memory?

Loop over the file object: for line in f:. It reads one line at a time instead of loading everything, unlike read() or readlines().

How do I check whether a file exists?

Use Path("file.txt").exists() from pathlib, or simply try to open the file and catch FileNotFoundError, which avoids a race between the check and the open.

Which encoding should I use?

UTF-8 for almost everything. Pass encoding="utf-8" to open() explicitly, so that the code behaves the same on every computer.

Run this code in your browser

The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.

Open Python Compiler

Stuck on a traceback?

AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.

Try AI Assist

Test yourself

Timed questions on Standard Library, Files and Regex, with an explanation for every answer.

Take the Quiz
Upskly AI Team
Learning made simple
Scroll to Top