You have likely used
open() to read or write files. If you forget to call .close(), the file handle stays open. This can lead to data loss or system errors, especially in long-running programs.The with statement
# Manual approach (risky)
file = open('data.txt', 'w')
file.write('hello')
# If an exception happens here, close() is never called!
file.close()
# Context manager approach (safe)
with open('data.txt', 'w') as file:
file.write('hello')
# File is closed automatically, even if an error occursThe
with statement ensures that cleanup code runs no matter what. It works by calling the object's __enter__ method when the block starts and its __exit__ method when the block ends. If your object defines these special methods, Python treats it as a context manager.Building custom context managers
import time
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield # This is where the 'with' block runs
elapsed = time.time() - start
print(f'Took {elapsed:.2f}s')
Handing something to the with block
from contextlib import contextmanager
@contextmanager
def notebook():
pages = []
yield pages # whatever you yield lands in the `as` variable
pages.append('filed')
with notebook() as pages:
pages.append('first note')
print(pages)
# ['first note', 'filed']
yield is what as binds.Whatever you
yield is handed to the name after as. The block then runs, and when it finishes control returns to the line straight after the yield, so anything you write there is your cleanup step.Cleanup that still happens after an error
from contextlib import contextmanager
@contextmanager
def session(log):
log.append('open')
try:
yield log
finally:
log.append('close') # runs even if the block raised
log = []
try:
with session(log):
raise ValueError('boom')
except ValueError:
pass
print(log)
# ['open', 'close']