Python with Statement
1The with Statement: overview
The with statement runs setup code before a block and guaranteed cleanup code after it, even when the block fails, by using an object called a context manager.
Before this lesson: Python Exceptions: try, except, else and finally, Python File Handling
2The with Statement: diagram
3The with Statement: explained step by step
Some jobs come in pairs: open a file and close it, lock a resource and release it, start a transaction and commit it. If your code forgets the second half, or an error jumps over it, files stay open and resources leak. A context manager is an object that does the setup and guarantees the cleanup. The with statement is how you use one.
with open("notes.txt", "w") as f:
f.write("hello")
What with really does
When Python meets with something as name: it follows three steps:
- It calls the object's
__enter__()method (the setup). Whatever__enter__returns is stored in the name afteras. - It runs the indented block.
- It calls
__exit__()(the cleanup) no matter how the block ended: normally, throughreturnorbreak, or because an exception escaped.
Written by hand, the same promise needs try/finally:
f = open("notes.txt", "w")
try:
f.write("hello")
finally:
f.close()
Every caller would have to remember that pattern. With a context manager the rule lives inside the object, and with is the short, readable form.
A file is closed even when the block fails
Every file object has a closed attribute, which lets you watch the cleanup happen.
with open("todo.txt", "w") as f:
f.write("Call Sam\n")
print("inside the block:", f.closed)
print("after the block:", f.closed)
try:
f.write("one more line")
except ValueError as err:
print("ValueError:", err)The name f still exists after the block, but the file behind it is closed. Do all the reading and writing inside the block. The same closing happens if the block raises an error, as the first example below shows.
Several context managers in one with
Separate them with commas. They are entered from left to right and exited in the opposite order.
with open("names.txt", "w") as f:
f.write("asha\nben\n")
with open("names.txt") as source, open("loud.txt", "w") as target:
for line in source:
target.write(line.upper())
with open("loud.txt") as f:
print(f.read().split())Writing your own with a class
A context manager is any object with an __enter__ and an __exit__ method. __exit__ receives the exception type, value and traceback; all three are None when the block succeeded. What it returns matters: True swallows the exception, while False (or None) lets it continue upward.
class Section:
def __init__(self, title):
self.title = title
def __enter__(self):
print("start", self.title)
return self
def __exit__(self, exc_type, exc_value, traceback):
problem = exc_type.__name__ if exc_type else "none"
print("end", self.title, "- error:", problem)
return False
with Section("import"):
print("working")
try:
with Section("export"):
1 / 0
except ZeroDivisionError:
print("the error was not hidden")Returning True is powerful and dangerous. If you ever use it, check exc_type so that you hide only the one error you expect.
The shortcut: @contextmanager
A class is a lot of code for a small job. The decorator contextlib.contextmanager turns a generator function into a context manager: the code before yield is the setup, the yielded value is what as receives, and the code after is the cleanup. Wrap the yield in try/finally. An error inside the with block is raised at the yield, so without finally the cleanup lines would be skipped.
from contextlib import contextmanager
settings = {"mode": "normal"}
@contextmanager
def temporary_mode(name):
old = settings["mode"]
settings["mode"] = name
try:
yield
finally:
settings["mode"] = old
with temporary_mode("debug"):
print("inside:", settings["mode"])
print("outside:", settings["mode"])Ready-made helpers
contextlib and io already contain handy ones. suppress(*errors) ignores only the errors you list. redirect_stdout(target) sends everything print() writes into a file-like object. io.StringIO is a text file that lives in memory: it has write(), read() and getvalue(), and it is perfect for tests.
import io
import os
from contextlib import suppress, redirect_stdout
with suppress(FileNotFoundError):
os.remove("missing.txt")
print("no crash")
buffer = io.StringIO()
with redirect_stdout(buffer):
print("captured text")
print(repr(buffer.getvalue()))When to use with
Use it whenever something has to be undone or released: files, locks, database connections and transactions, temporary changes to a setting, or output you want to capture. If a task needs "do this, then always do that", ask whether it should be a context manager. The files in this lesson live in the browser's temporary file system, but on your own computer the same code works on real files.
Quick reference: The with Statement
- Python File Handling Cheat Sheet: open, read, write, CSV, JSON — Open files with `with open(path, mode, encoding="utf-8") as f:` so they close automatically, read with `read(…
- How to Read a File Line by Line in Python — Open the file with `with open("file.txt") as f:` and loop over it: `for line in f:`. Each `line` is one line …
- How to Write to a File in Python — Use `with open("notes.txt", "w", encoding="utf-8") as f:` then `f.write("text\n")`. Mode `"w"` replaces the f…
- Python open() Function — `open(file, mode='r', encoding=None)` opens a file and returns a file object you can read from or write to; u…
4The with Statement: syntax
with open("data.txt") as f: # __enter__ runs; f is what it returned
text = f.read() # do all the work inside the block
# __exit__ runs here, always
with open("in.txt") as src, open("out.txt", "w") as dst: # two managers
dst.write(src.read())
class Managed: # class-based context manager
def __enter__(self):
... # setup
return self # bound to the name after "as"
def __exit__(self, exc_type, exc_value, traceback):
... # cleanup (all three are None if no error)
return False # True would swallow the exception
from contextlib import contextmanager, suppress, redirect_stdout
@contextmanager
def managed(): # generator-based version
... # setup
try:
yield value # value is bound by "as"
finally:
... # cleanup, even after an error
with suppress(FileNotFoundError): # ignore only this error
os.remove("old.tmp")
with redirect_stdout(buffer): # print() writes into buffer
print("captured")
buffer = io.StringIO() # in-memory text file: write(), getvalue()
5The with Statement: run the example
Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.
with open("scores.txt", "w") as f:
f.write("Asha,82\nBen,oops\nChen,91\n")
total = 0
try:
with open("scores.txt") as f:
for line in f:
name, score = line.strip().split(",")
total += int(score)
except ValueError as err:
print("Bad data:", err)
print("Total before the error:", total)
print("File closed?", f.closed)Expected outputBad data: invalid literal for int() with base 10: 'oops'
Total before the error: 82
File closed? True
7The with Statement: how the example works
Hover or tap a step to highlight its lines in the example above.
- Line 1-2A file is written inside a
withblock and closed automatically when the block ends. The middle line,Ben,oops, has a score that is not a number. - Line 4
totalcollects the scores that are read successfully. - Line 5-6The
tryblock opens the file for reading.__enter__returns the file object, which is stored inf. - Line 7-8The loop reads one line at a time.
strip()removes the newline andsplit(",")separates the name from the score. - Line 9
int(score)converts the text and adds it tototal. Asha's82works, but on the second lineint("oops")raises aValueError. The error leaves the loop and thewithblock, and on its way out__exit__closes the file. - Line 10-11The
exceptblock catches the error and prints its message. Chen's score was never reached. - Line 13Only Asha's score made it into
total: 82. - Line 14
f.closedisTrue: the file was closed even though an error escaped from inside the block. That guarantee is the whole point ofwith.
8The with Statement: common mistakes
Using the file after the with block
✗ Wrong
with open("scores.txt", "w") as f:
f.write("Asha,90\n")
with open("scores.txt") as f:
print("Reading the file...")
text = f.read()
print(text)✓ Right
with open("scores.txt", "w") as f:
f.write("Asha,90\n")
with open("scores.txt") as f:
print("Reading the file...")
text = f.read()
print(text)The file is closed the moment the indented block ends. Move every read or write that needs the file inside the block (indent text = f.read()).
Cleanup without try/finally in @contextmanager
✗ Wrong
from contextlib import contextmanager
locked = []
@contextmanager
def lock(name):
locked.append(name)
yield
locked.remove(name)
try:
with lock("report"):
raise RuntimeError("crash")
except RuntimeError:
pass
print(locked)✓ Right
from contextlib import contextmanager
locked = []
@contextmanager
def lock(name):
locked.append(name)
try:
yield
finally:
locked.remove(name)
try:
with lock("report"):
raise RuntimeError("crash")
except RuntimeError:
pass
print(locked)An error in the with block is raised at the yield, so the lines after it never run. Put the yield inside try and the cleanup inside finally.
An __exit__ method with the wrong parameters
✗ Wrong
class Timer:
def __enter__(self):
print("start")
return self
def __exit__(self):
print("stop")
with Timer():
print("working")✓ Right
class Timer:
def __enter__(self):
print("start")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("stop")
with Timer():
print("working")Python always passes the exception type, value and traceback to __exit__, so it needs three parameters besides self, even if you ignore them.
Returning True from __exit__ and hiding every error
✗ Wrong
class Quiet:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
print("cleaning up")
return True
with Quiet():
number = int("abc")
print("finished")✓ Right
class Quiet:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
print("cleaning up")
return False
try:
with Quiet():
number = int("abc")
except ValueError as err:
print("Handled:", err)
print("finished")Returning True tells Python the exception was handled. Return False (or nothing) to let errors through, or return True only for the one exception you really want to hide, for example return exc_type is ValueError.
Using with on something that is not a context manager
✗ Wrong
with "notes.txt" as f:
print(f.read())✓ Right
with open("notes.txt", "w") as f:
f.write("remember the milk\n")
with open("notes.txt") as f:
print(f.read())with needs an object that has __enter__ and __exit__. A file name is just a string; call open() to get a file object, which is a context manager.
9The with Statement: exercises
Write your answer, then press Check. The checks run in your browser.
1. Save and load a name list
Write code Easy ✓ CompletedWrite two functions, both using a with block:
save_names(path, names)writes each name on its own line (a later call replaces the old content).load_names(path)returns the names as a list of strings, without the newline characters.
The code then saves and loads the starter list and prints the result.
def save_names(path, names):
# write one name per line
pass
def load_names(path):
# return a list of names without the newlines
return []
save_names("team.txt", ["Asha", "Ben", "Chen"])
print(load_names("team.txt"))
def save_names(path, names):
with open(path, "w") as f:
for name in names:
f.write(name + "\n")
def load_names(path):
with open(path) as f:
return [line.strip() for line in f]
save_names("team.txt", ["Asha", "Ben", "Chen"])
print(load_names("team.txt"))
2. Predict the guard
Predict the output Medium ✓ CompletedRead the class and the two with blocks, then type exactly what the program prints. Think about what __exit__ returns in each case. Then press Check.
class Guard:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("exit:", exc_type.__name__ if exc_type else "clean")
return exc_type is KeyError
with Guard():
print("body one")
with Guard():
{}["missing"]
print("body two")
print("after")
enter
body one
exit: clean
enter
exit: KeyError
after
2 more exercises → 6 more examples
Questions learners ask
Do I still need to call f.close() when I use with open(...)?
No. Leaving the with block closes the file for you, even if an error happens inside the block. Calling close() as well does no harm, but it is unnecessary. If you open a file without with, you must close it yourself, and you must make sure that happens even when an error occurs.
What are the three arguments of __exit__, and what does returning True do?
They are the exception type, the exception value and the traceback. All three are None when the block finished without an error. If exit returns True, Python treats the exception as handled and carries on after the with block; if it returns False or None, the exception continues upward. Return True only for a specific error you intend to hide, because it can bury real bugs.
Is the with statement only for files?
No. Any object that has enter and exit works: locks, database connections and transactions, temporary directories, temporary changes to a setting, and tools like suppress() and redirect_stdout(). Files are just the most common example because forgetting to close them is such an easy mistake.
Further reading
10Next lesson
Up nextPython pathlibpathlib's Path is an object that represents a file or folder location, with operators and methods to join, take apart, …Related:
Python Exceptions: try, except, else and finallyPython File HandlingPython pathlibPython Class Methods and Static Methods
← Python File Handling · All Iteration and Files lessons · Practise in the IDE