File Read/Write
Instead of only printing output to the terminal, you can have the program save data to a file on your computer so data stays after the program ends, or read data from a file.
For how to pull code from another .py file into your program, that's in Modules & Imports.
flowchart LR
program@{ shape: procs, label: "your_program.py" }
file@{ shape: paper-tape, label: "notes.txt" }
file -->|"read()"| program
program -->|"write()"| file
FIG: a program reading from and writing to a file
Opening and closing files
open() returns a file object to read from or write to.
File paths
open("notes.txt", ...) is a relative path — Python looks for notes.txt in the program's working directory, the folder it's currently running from, which isn't necessarily the folder the .py file itself lives in. Reaching a file somewhere else means either writing out the folders in between, or an absolute path — the full location starting from the filesystem's root, which works the same no matter what the working directory is.
Say notes.txt is inside a snake_data folder in your Documents folder instead of next to your script. To find its exact path:
Right-click the file in Finder, hold ⌥ Option, and choose Copy "notes.txt" as Pathname — or drag the file straight into a Terminal window to have the path typed out for you.
/Users/luka/Documents/snake_data/notes.txt
Shift+right-click the file in File Explorer and choose Copy as path.
C:\Users\luka\Documents\snake_data\notes.txt
That path is what goes inside open():
with open("/Users/luka/Documents/snake_data/notes.txt", "r") as file:
print(file.read())
On Windows, write the path with an r prefix (r"C:\Users\luka\...") or doubled backslashes ("C:\\Users\\luka\\...") — a single backslash inside a normal string starts an escape sequence, which isn't what a Windows path means.
Every runnable example on this page opens a plain filename like "notes.txt" — that's a relative path into the sandbox's own working directory, the same reason it works without ever specifying a folder.
with
with runs the indented block below it, then closes the file automatically once the block ends — whether it finishes normally or raises an error partway through. open(...) produces the file object; as file is what makes it available under that name inside the block.
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft")
print("saved") # file is already closed here — "saved" sits outside the with block
Without with, the same thing takes an explicit .close() call — easy to forget, and skipping it means what you wrote might not be saved to the file yet — Python can hold new content in memory for a while before actually writing it out — or the file stays locked for anything else trying to open it.
file = open("notes.txt", "w")
file.write("ball python, 4.5 ft")
file.close() # easy to forget
Run a with example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft")
print("saved")
Modes options
The second argument to open() is the mode — what you intend to do with the file:
| Mode | Meaning |
|---|---|
"r" |
Read (default) — the file must already exist |
"w" |
Write — creates the file if it doesn't exist, erases its contents if it does |
"a" |
Append — creates the file if it doesn't exist, adds to the end if it does |
"x" |
Create — creates the file, but raises an error if it already exists |
Read
Modes
"r" read existing
Say notes.txt already exists — written by an earlier run, or typed by hand in a text editor — and looks like this, one snake per line:
ball python, 4.5 ft
burmese python, 12 ft
boa, 8 ft
Opening a file that doesn't exist in "r" mode raises FileNotFoundError instead of creating one — unlike "w"/"a"/"x", "r" never creates a file.
with open("notes.txt", "r") as file:
print(file.read())
open("missing.txt", "r") # FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt'
Run a read existing example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n")
with open("notes.txt", "r") as file:
print(file.read())
try:
open("missing.txt", "r")
except FileNotFoundError as e:
print(e)
Functions
Whole file
.read() returns the whole thing as one string, newlines and all. It also takes an optional character count, returning just that many characters instead of the whole file.
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n")
with open("notes.txt", "r") as file:
text = file.read()
print(text)
with open("notes.txt", "r") as file:
print(file.read(4)) # "ball"
Run a whole file example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n")
with open("notes.txt", "r") as file:
text = file.read()
print(text)
with open("notes.txt", "r") as file:
print(file.read(4))
By line
.readlines() returns a list, one string per line, each still ending in a trailing \n. Looping over the file object directly reads it the same way, one line at a time, without holding the whole list in memory at once. .readline() reads a single line and advances to the next — call it repeatedly to step through a file by hand, though looping does the same thing more naturally.
with open("notes.txt", "r") as file:
lines = file.readlines()
print(lines) # ["ball python, 4.5 ft\n", "burmese python, 12 ft\n", "boa, 8 ft\n"]
with open("notes.txt", "r") as file:
for line in file:
print(line.strip()) # ball python, 4.5 ft / burmese python, 12 ft / boa, 8 ft
Run a line-by-line example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n")
with open("notes.txt", "r") as file:
lines = file.readlines()
print(lines)
with open("notes.txt", "r") as file:
for line in file:
print(line.strip())
For efficiency, loop over a file instead of reading it all at once
| Time | Space | |
|---|---|---|
.read() / .readlines() |
O(n) | O(n) |
| Loop over the file, line by line | O(n) | O(1) |
.read()/.readlines() holds the entire file's contents in memory at once (O(n) space). Looping over the file object or calling .readline() repeatedly needs only enough memory for the current line, O(1) space regardless of file size.
For a small file it doesn't matter; for a file too large to comfortably fit in memory, it's the difference between the program running and it not.
See Efficiency for why this distinction matters.
Seek and tell
.tell() returns the current position in the file, as a character count from the start. .seek(position) moves back to a given position, letting you re-read part of a file without closing and reopening it.
with open("notes.txt", "r") as file:
file.read()
print(file.tell()) # 52 — at the end, after reading everything
file.seek(0)
print(file.read(4)) # "ball" — back at the start
Run a seek and tell example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n")
with open("notes.txt", "r") as file:
file.read()
print(file.tell())
file.seek(0)
print(file.read(4))
Write
Modes
"w" overwrite
"w" erases whatever was already in the file before writing anything new — opening a file you meant to add to with "w" is a common way to accidentally lose data.
flowchart LR
w1@{ shape: paper-tape, label: "ball python, 4.5 ft" } -->|"w": erased, then written| w2@{ shape: paper-tape, label: "blood python, 3 ft" }
FIG: "w" erases the file, then writes the new content
species = ["ball python", "burmese python", "boa"]
with open("notes.txt", "w") as file:
for s in species:
file.write(s + "\n")
Run a writing multiple lines example
All the examples above, combined into one script:
species = ["ball python", "burmese python", "boa"]
with open("notes.txt", "w") as file:
for s in species:
file.write(s + "\n")
with open("notes.txt", "r") as file:
print(file.read())
"a" append
Use "a" instead to add to the end, keeping the existing contents in place — compare against "w" above.
flowchart LR
a1@{ shape: paper-tape, label: "ball python, 4.5 ft" } -->|"a": kept, plus written| a2@{ shape: paper-tape, label: "ball python, 4.5 ft<br/>blood python, 3 ft" }
FIG: "a" keeps the file's contents, then adds the new content to the end
with open("notes.txt", "a") as file:
file.write("blood python, 3 ft\n")
with open("notes.txt", "r") as file:
print(file.read())
Run an appending vs. overwriting example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
for s in ["ball python", "burmese python", "boa"]:
file.write(s + "\n")
with open("notes.txt", "a") as file:
file.write("blood python, 3 ft\n")
with open("notes.txt", "r") as file:
print(file.read())
"x" create
"x" is for when overwriting an existing file would be a mistake — it creates the file, but raises FileExistsError instead of silently replacing something already there. Like "w", it's write-only — reading from that same file object raises an error, so reading it back means reopening it in "r" mode afterward.
with open("newfile.txt", "x") as file:
file.write("hello")
with open("newfile.txt", "r") as file:
print(file.read()) # "hello"
open("newfile.txt", "x") # FileExistsError: [Errno 17] File exists: 'newfile.txt'
Run an x mode example
All the examples above, combined into one script:
with open("newfile.txt", "x") as file:
file.write("hello")
with open("newfile.txt", "r") as file:
print(file.read())
try:
with open("newfile.txt", "x") as file:
file.write("hello again")
except FileExistsError as e:
print(e)
Functions
Single string
.write() writes a string to the file — it doesn't add a newline for you, so add one yourself at the end of each line, usually by looping over a list. Whether that write starts the file fresh or adds onto what's already there depends on which mode you opened it with, "w" or "a".
with open("notes.txt", "w") as file:
file.write("ball python")
file.write("4.5 ft")
with open("notes.txt", "r") as file:
print(file.read()) # "ball python4.5 ft" — no newline between the two writes
Run a single string example
All the examples above, combined into one script:
with open("notes.txt", "w") as file:
file.write("ball python")
file.write("4.5 ft")
with open("notes.txt", "r") as file:
print(file.read())
Multiple strings
.writelines() takes a list of strings and writes them all in one call instead of looping yourself — like .write(), it doesn't add newlines, so they need to already be in the strings.
species = ["ball python\n", "burmese python\n", "boa\n"]
with open("notes.txt", "w") as file:
file.writelines(species)
Run a writelines example
All the examples above, combined into one script:
species = ["ball python\n", "burmese python\n", "boa\n"]
with open("notes.txt", "w") as file:
file.writelines(species)
with open("notes.txt", "r") as file:
print(file.read())
Related libraries
Everything above is plain text. For other file formats, these Libraries pages build on the same open() and file-mode basics covered here:
| Library | Use for |
|---|---|
| csv | Reading and writing spreadsheets. |
| json | Reading and writing JSON data: nested dicts and lists, saved to a file or a string. |
| Pillow | Opening, editing, and saving images, built around one Image object. |
| OpenCV | Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. |
| Matplotlib | Charts and plots: line, bar, and scatter, built directly from plain Python data. |
| tkinter | Creating desktop applications: text, buttons, dropdowns, forms, output, etc. |