The with Statement and Resource Cleanup
18 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Files, locks and database connections must be given back after use, even if the code between fails. The with statement makes this cleanup automatic.
Cleanup must always happen
Resource is something a program borrows and must give back, such as a file, a network connection, a lock or a database transaction.
An open file uses an operating-system handle. If an error skips the close() call, the handle stays open. The safe plain-Python fix is try with finally, which always runs.
log = open("orders.txt", "w")try: log.write("order 1\n")finally: log.close()This works, but every use needs the same four lines, and it is easy to forget the finally.
The with statement
Context manager is an object that sets something up when a with block starts and cleans it up when the block ends, whether the block finished normally or raised an error.
The with statement does the setup, runs your block and does the cleanup, even if the block fails. The object it works with is a context manager.
with open("orders.txt", "w") as log: log.write("order 1\n")
print(log.closed)Example 1 · try/finally and with side by side
import osimport tempfile
with tempfile.TemporaryDirectory() as folder: path = os.path.join(folder, "orders.txt")
log = open(path, "w") try: log.write("order 1\n") finally: log.close() print("try/finally closed:", log.closed)
with open(path, "a") as log: log.write("order 2\n") print("with closed:", log.closed)try/finally closed: True with closed: True
What this code does
- Both versions close the file; the
withversion is shorter and cannot forget. - The temporary folder is itself a context manager and is removed at the end.
Example 2 · Open inside, closed outside
import osimport tempfile
with tempfile.TemporaryDirectory() as folder: path = os.path.join(folder, "marks.txt") with open(path, "w") as f: f.write("72\n85\n91\n") print("inside:", f.closed) print("outside:", f.closed) with open(path) as f: marks = [int(line) for line in f] print(sum(marks) / len(marks))inside: False outside: True 82.66666666666667
What this code does
f.closedflips from False to True the moment the block ends.- Reading with a
withblock and a list comprehension is the usual pattern for small files.
After the block, the file is closed. The name log still exists, but the file behind it is released.
Cleanup runs even after an error
If the block raises an exception, the cleanup still runs first. Then the exception continues upward. A lock is a good example: it must never stay locked.
import threading
lock = threading.Lock()try: with lock: raise ValueError("bad order")except ValueError: print(lock.locked())Example 3 · A lock is freed after an error
import threading
lock = threading.Lock()
try: with lock: print("locked:", lock.locked()) raise ValueError("bad order")except ValueError as err: print("error:", err)
print("locked after:", lock.locked())locked: True error: bad order locked after: False
What this code does
- The error travels up, but the lock is released on the way.
- This is why
with lock:is preferred overacquire()andrelease().
The lock is released before the except line runs, so it prints False. With a plain lock.acquire() and no finally, it would stay locked.
Several resources in one with
Put several managers in one statement, separated by commas. They are cleaned up in the reverse order of opening, like a stack.
with ( open("in.txt") as source, open("out.txt", "w") as target,): target.write(source.read())Example 4 · Two files in one statement
import osimport tempfile
with tempfile.TemporaryDirectory() as folder: src = os.path.join(folder, "in.txt") dst = os.path.join(folder, "out.txt") with open(src, "w") as f: f.write("hello")
with open(src) as source, open(dst, "w") as target: target.write(source.read().upper())
with open(dst) as f: print(f.read())HELLO
What this code does
- The second file opens after the first and closes before it.
- Both are closed even if the copy fails halfway.
Both files are closed at the end. The brackets let you split the line without a backslash (Python 3.10 and newer).
Next step
Check your understanding
12 quick questions