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:

  1. with open('file.txt') as f:
  2. 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:

  1. class CustomOpen(object):
  2. def __init__(self, filename):
  3. self.file = open(filename)
  4.  
  5. def __enter__(self):
  6. return self.file
  7.  
  8. def __exit__(self, ctx_type, ctx_value, ctx_traceback):
  9. self.file.close()
  10.  
  11. with CustomOpen('file') as f:
  12. 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:

  1. from contextlib import contextmanager
  2.  
  3. @contextmanager
  4. def custom_open(filename):
  5. f = open(filename)
  6. try:
  7. yield f
  8. finally:
  9. f.close()
  10.  
  11. with custom_open('file') as f:
  12. 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 yieldstatement. 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.