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
- Writing and reading a text file
- File modes: r, w, a and friends
- Three ways to read: read, readline, readlines
- Reading line by line (the best way for big files)
- Appending vs overwriting
- Encoding: characters vs bytes
- pathlib: a nicer way to handle paths
- CSV and JSON in a few lines
- When the file is missing
- Common mistakes
- Try it yourself
- FAQ
The short version
with open("file.txt", "w") as f:thenf.write(text)to write.with open("file.txt") as f:thenf.read()orfor line in f:to read.- Modes:
rread (default),wwrite and erase,aappend,xcreate only. - Always use
withso the file is closed, and give anencodingfor 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
| Mode | Meaning | If the file does not exist | Existing content |
|---|---|---|---|
| r | Read (the default) | Error | Kept |
| w | Write | Created | Erased first |
| a | Append | Created | Kept, new text goes at the end |
| x | Create only | Created | Error if the file already exists |
| r+ | Read and write | Error | Kept |
| w+ | Write and read | Created | Erased 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
32. 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
xAppend 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
60The 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.gzThe 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.
Related reading
- Python Context Managers and the with Statement – why with closes files for you.
- Python Exception Handling: try, except, finally – handle missing files gracefully.
- Python Strings: Methods and Slicing Explained – the text you read from files.
- Python Dictionaries Explained With Examples – what JSON data becomes in Python.
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.
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.
Test yourself
Timed questions on Standard Library, Files and Regex, with an explanation for every answer.