Context Managers
A context manager is a Python object that provides extra contextual informationto an action. This extra information takes the form of running a callable uponinitiating the context using the with
statement, as well as running a callableupon completing all the code inside the with
block. The most well knownexample of using a context manager is shown here, opening on a file:
- with open('file.txt') as f:
- contents = f.read()
Anyone familiar with this pattern knows that invoking open
in this fashionensures that f
’s close
method will be called at some point. This reducesa developer’s cognitive load and makes the code easier to read.
There are two easy ways to implement this functionality yourself: using a classor using a generator. Let’s implement the above functionality ourselves, startingwith the class approach:
- class CustomOpen(object):
- def __init__(self, filename):
- self.file = open(filename)
- def __enter__(self):
- return self.file
- def __exit__(self, ctx_type, ctx_value, ctx_traceback):
- self.file.close()
- with CustomOpen('file') as f:
- contents = f.read()
This is just a regular Python object with two extra methods that are usedby the with
statement. CustomOpen is first instantiated and then itsenter
method is called and whatever enter
returns is assigned tof
in the as f
part of the statement. When the contents of the with
blockis finished executing, the exit
method is then called.
And now the generator approach using Python’s owncontextlib:
- from contextlib import contextmanager
- @contextmanager
- def custom_open(filename):
- f = open(filename)
- try:
- yield f
- finally:
- f.close()
- with custom_open('file') as f:
- contents = f.read()
This works in exactly the same way as the class example above, albeit it’smore terse. The customopen
function executes until it reaches the yield
statement. It then gives control back to the with
statement, which assignswhatever was yield
’ed to _f in the as f
portion. The finally
clauseensures that close()
is called whether or not there was an exception insidethe with
.
Since the two approaches appear the same, we should follow the Zen of Pythonto decide when to use which. The class approach might be better if there’sa considerable amount of logic to encapsulate. The function approachmight be better for situations where we’re dealing with a simple action.