with open("file.txt") as file: is how Python opens a file safely. The with block gives you the file object as file, and Python closes it automatically when the block ends, even if an error happens inside:
with open("stores.txt", encoding="utf-8") as file:
text = file.read()
# file is closed here
You’ll see real output from Python 3.12.5 on Windows 11 in every Command Prompt screenshot below.
I’ll cover reading and writing, the file modes, what happens on errors, the encoding trap on Windows, opening two files at once, and writing your own context manager.
How with open as file works in Python
open() returns a file object, as file names it, and the indented block uses it. Check file.closed and you can see exactly when Python closes it:
with open("stores.txt", "w", encoding="utf-8") as file:
file.write("Austin, TX\n")
file.write("Denver, CO\n")
file.write("Boston, MA\n")
with open("stores.txt", encoding="utf-8") as file:
text = file.read()
print("inside with, closed?", file.closed)
print(text, end="")
print("after with, closed? ", file.closed)
Output:
inside with, closed? False
Austin, TX
Denver, CO
Boston, MA
after with, closed? True

closed is False; right after it, True. You never call close() yourself.The variable still exists after the block, so text is usable. The file itself is closed, though, so reading from it again would raise a ValueError.
Reading a file line by line with with open
Looping over the file object reads one line at a time, which keeps memory low on big files. Use read().splitlines() when you want the whole thing as a list:
with open("stores.txt", "w", encoding="utf-8") as f:
f.write("Austin, TX\nDenver, CO\nBoston, MA\n")
with open("stores.txt", encoding="utf-8") as f:
for n, line in enumerate(f, start=1): # one line at a time, low memory
city, state = line.strip().split(", ")
print(n, city, "->", state)
with open("stores.txt", encoding="utf-8") as f:
print(f.read().splitlines()) # whole file as a list, no newlines
Output:
1 Austin -> TX
2 Denver -> CO
3 Boston -> MA
['Austin, TX', 'Denver, CO', 'Boston, MA']
Each line keeps its \n, so strip() it before splitting. There’s more in reading a file line by line in Python.
File modes: r, w, a and x
The second argument to open() sets the mode. Be careful with "w": it empties the file the moment it opens it:
with open("log.txt", "w", encoding="utf-8") as f: # w: create or wipe
f.write("09:00 store opened\n")
with open("log.txt", "a", encoding="utf-8") as f: # a: add to the end
f.write("17:30 store closed\n")
with open("log.txt", encoding="utf-8") as f: # r is the default
print(f.read(), end="")
try:
with open("log.txt", "x", encoding="utf-8") as f: # x: only if it doesn't exist
f.write("never written")
except FileExistsError as err:
print("FileExistsError:", err)
Output:
09:00 store opened
17:30 store closed
FileExistsError: [Errno 17] File exists: 'log.txt'

a added the second line, and x refused to touch a file that already exists.| Mode | Meaning | If the file exists | If it doesn’t |
|---|---|---|---|
"r" | Read (default) | Opens it | FileNotFoundError |
"w" | Write | Wipes it | Creates it |
"a" | Append | Writes at the end | Creates it |
"x" | Exclusive create | FileExistsError | Creates it |
"rb", "wb" | Binary read or write | Bytes, no encoding | Same as r / w |
"r+" | Read and write | Opens it | FileNotFoundError |
What happens if an error occurs inside with open?
The file still gets closed. That’s the whole point of with: cleanup runs whether the block finishes normally or raises. A missing file fails earlier, at open():
try:
with open("orders_2026.csv", encoding="utf-8") as f:
print(f.read())
except FileNotFoundError as err:
print("FileNotFoundError:", err)
try:
with open("receipt.txt", "w", encoding="utf-8") as f:
f.write("Total: $42.50\n")
raise ValueError("card declined") # something goes wrong mid-way
except ValueError as err:
print("ValueError:", err, "| file closed anyway?", f.closed)
Output:
FileNotFoundError: [Errno 2] No such file or directory: 'orders_2026.csv'
ValueError: card declined | file closed anyway? True
Wrap the with in try when a missing file is a normal case, like an optional config. See checking if a file exists in Python for other approaches.
Always pass encoding=”utf-8″ to open() on Windows
Without encoding=, open() uses your system’s default. On a US Windows PC that’s usually cp1252, so UTF-8 text like “Café” or an en dash comes back garbled:
import locale
with open("menu.txt", "w", encoding="utf-8") as f:
f.write("Café latte – $4.75\n")
print("Windows default encoding:", locale.getencoding())
with open("menu.txt", encoding="cp1252") as f: # what you get without encoding= here
print("wrong:", f.read(), end="")
with open("menu.txt", encoding="utf-8") as f:
print("right:", f.read(), end="")
Output:
Windows default encoding: cp1252
wrong: Café latte – $4.75
right: Café latte – $4.75

encoding="utf-8" reads it back correctly.I add encoding="utf-8" to every text-mode open() call. It makes your script behave the same on Windows, macOS and Linux.
Opening two files in one with statement
Separate the files with commas. Since Python 3.10 you can wrap them in parentheses and put each on its own line, which reads much better:
with open("prices.txt", "w", encoding="utf-8") as f:
f.write("19.99\n5.49\n102.00\n")
with (
open("prices.txt", encoding="utf-8") as src,
open("prices_with_tax.txt", "w", encoding="utf-8") as dst,
):
for line in src:
dst.write(f"{float(line) * 1.08:.2f}\n")
with open("prices_with_tax.txt", encoding="utf-8") as f:
print(f.read().split())
Output:
['21.59', '5.93', '110.16']

Writing your own context manager
open() isn’t special. Any object with __enter__ and __exit__ works with with, and contextlib.contextmanager turns a generator into one:
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield
finally:
print(f"{label} took {time.perf_counter() - start:.3f} s")
with timer("writing 10,000 lines"):
with open("big.txt", "w", encoding="utf-8") as f:
for i in range(10_000):
f.write(f"order {i}\n")
with open("big.txt", encoding="utf-8") as f:
print(sum(1 for _ in f), "lines written")
Output:
writing 10,000 lines took 0.004 s
10000 lines written
The code before yield runs on entry, and the finally part runs on exit. Locks, database connections and timers all use this pattern. For writing other data types, see writing a list to a file.
Here are more file guides you can try next:
- Open a file in Python
- Write a string to a file
- Create a new file in Python
- Save a dictionary as a JSON file
- What does enumerate do in Python
Frequently asked questions
What does with open as file mean in Python?
It opens a file, names the file object (here file), and closes it automatically when the indented block ends.
Do I need to call close() with a with statement?
No. The with statement closes the file for you, even if an exception is raised inside the block.
How do I append to a file instead of overwriting it?
Open it in append mode: with open("log.txt", "a", encoding="utf-8") as f:.
Can I open multiple files with one with statement?
Yes. Separate them with commas, or wrap them in parentheses on separate lines in Python 3.10 and newer.
Why does my file show strange characters?
It was read with the wrong encoding. Pass encoding="utf-8" to open().
Where are all the open() options documented?
Modes, buffering, encoding and newline handling are all in the open() function docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn