Context Managers and Else Blocks
Notes based on Fluent Python, summarized and rephrased in my own words.[file:23]
Else blocks beyond if
Python’s else is not only for if. It also appears in:
for ... elsewhile ... elsetry ... else
Their meaning differs from if/else and is easy to misunderstand.[file:23]
for ... else
For loops:
- The
elseblock runs only if the loop terminates normally (i.e. not viabreak).[file:23]
Example: searching for a value, and erroring only if not found:
my_list = [1, 2, 3, 4, 5]
for item in my_list:
if item == 'banana':
break
else:
raise ValueError('No banana flavor found!')
print('finished search!')- If the loop finishes without hitting
break,elseruns and raises the error.[file:23] - If we change the list to include
'banana', the loopbreaks and theelseis skipped, so no error is raised.
my_list = [1, 2, 3, 'banana', 5]
for item in my_list:
if item == 'banana':
break
else:
raise ValueError('No banana flavor found!')
print('finished search!') # printedConceptually, for ... else means: "loop, then do this if we never broke out early".[file:23]
while ... else
For while loops:[file:23]
- The
elseblock runs only if the loop ended because the condition became false, not because of abreak.
This can be used for loops where a break indicates some exceptional or early-exit case, and the else handles the "normal completion" path.
try ... else
For try blocks:[file:23]
- The
elseblock runs only if no exception was raised in thetryblock. - Exceptions that occur inside the
elseblock are not handled by the precedingexceptclauses.[file:23]
try/else is useful when you want to:
- Keep the
tryblock small, containing only the code that might raise the exception you are catching. - Put the rest of the normal-path code in the
elseblock, making the structure clearer.
A note on readability
The book’s author argues that using else with loops and try is somewhat unfortunate naming, because in for/while/try it reads more like "then" rather than "otherwise".[file:23] However, the feature is established, and learning the pattern makes code involving searches and guarded operations more expressive.
EAFP vs LBYL coding styles
Two common styles in Python:
EAFP – Easier to Ask Forgiveness than Permission.
- Assume what you need is available and catch exceptions if it isn’t.
- Code tends to have more
try/exceptblocks.
LBYL – Look Before You Leap.
- Check preconditions explicitly with
ifbefore doing something. - Code tends to have many conditionals.
- Check preconditions explicitly with
Example of LBYL:
if key in mapping:
return mapping[key]In multithreaded code, LBYL can suffer from race conditions between the check and the action (e.g. key removed after the if but before the return).[file:23]
EAFP often avoids this:
try:
return mapping[key]
except KeyError:
... # handle missing key- EAFP is idiomatic in Python and composes naturally with exception handling.
- LBYL is still useful, but you should be aware of concurrency issues and duplication of logic.
Context managers and the with statement
A context manager exists to manage the setup and teardown around a block of code, driven by the with statement—much like an iterator exists to support for.[file:23]
The main purpose of with is to simplify the common try/finally pattern used to guarantee cleanup:
# try/finally pattern
resource = acquire_resource()
try:
use(resource)
finally:
resource.close() # always executedWith a context manager:
with acquire_resource() as resource:
use(resource)
# cleanup happens automaticallyThe context manager protocol
A context manager object must implement two methods:[file:23]
__enter__(self)- Called at the start of the
withblock. - Its return value is bound to the
astarget (if present).
- Called at the start of the
__exit__(self, exc_type, exc_val, exc_tb)- Called after the
withblock finishes, whether it exits normally or due to an exception. - Plays the role of
finallyby performing cleanup. - If it returns
True, it suppresses the exception; otherwise, the exception propagates.
- Called after the
This protocol ensures that cleanup runs even if the block exits via return, break, continue, or an exception.
Helpful tools in contextlib
The contextlib module in the standard library provides utilities for building and working with context managers.[file:23]
Key helpers:
@contextlib.contextmanager- A decorator to turn a generator function into a context manager.
contextlib.closing(obj)- Wraps an object that has a
close()method so it can be used in awithblock and automatically closed.
- Wraps an object that has a
contextlib.redirect_stdout(target)/contextlib.redirect_stderr(target)- Temporarily redirect
sys.stdoutorsys.stderrto a file or file-like object inside awithblock.
- Temporarily redirect
contextlib.suppress(*exceptions)- Suppress specific exceptions inside the block.
contextlib.ExitStack- A flexible context manager for dynamically entering and exiting a variable number of other context managers and callbacks.
contextlib.nullcontext()- A no-op context manager, useful when you need a context manager in some cases but not others.
Example: a custom context manager with @contextmanager
Using @contextlib.contextmanager, you write a generator that:
- Sets up the context.
yields a value for thewithblock to use.- Performs cleanup after the block finishes.
Example that temporarily reverses stdout output:[file:23]
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
try:
yield 'JABBERWOCKY'
finally:
sys.stdout.write = original_writeUsage:
with looking_glass() as what:
print('Hello, world!')
print(what)
print('Back to normal')Inside the with block, printed text appears reversed, and what is set to 'JABBERWOCKY'. After the block, sys.stdout.write is restored.[file:23]
This pattern is often more convenient than writing a full class with __enter__ and __exit__ methods.