Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

Write more pythonic code with context managers

Batteries included is a blog series about the Python Standard Library. Each day, I share insights, ideas and examples for different parts of the library. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.

In Python community, we often talk about writing pythonic code. What that exactly means is up for interpretation but in essence, it’s about writing Python code that takes advantage of Python’s features and language constructs in the best way. Sometimes, it’s used in the context of discussing PEP 8 which is the Python style guide but that’s a rather surface level description.

Long-time Python core developer Raymond Hettinger has a wonderful talk about pythonic code from PyCon US 2015 called Beyond PEP 8 which is highly recommended watching for everyone who writes Python.

Today’s post is about Context Managers, a construct in Python language that makes the code easier to read and understand, making it more pythonic. To quote the documentation:

A context manager is an object that defines the runtime context to be established when executing a with statement. The context manager handles the entry into, and the exit from, the desired runtime context for the execution of the block of code.

If you have been writing Python somewhat recently, the most likely case where you have encountered them is in the context of opening files:

with open('filename.txt', 'r') as datafile:
  for line in datafile:
    print(line)

There’s nothing preventing you from achieving the same without context managers:

datafile = open('filename.txt', 'r')
for line in datafile:
  print(line)
datafile.close()

but I would argue that the context manager makes it clearer to read and understand. And it’s less likely you forget to close the reader after you’re done with it as this context manager closes the file at the end of the block.

Any class can be made into a context manager by defining two methods: __enter__ and __exit__. When the context manager is entered in the with block, the __enter__ method is run and when the block is finished, the __exit__ method is run.

In the aforementioned Raymond Hettinger talk, he has a great example of making code more pythonic by wrapping networking code initializing and finalizing into a context manager (note that this is Python 2 code):

from nettools import NetworkElement

with NetworkElement('171.0.2.45') as ne:
  for route in ne.routing_table:
    print '%15s -> %s' % (route.name, route.ipaddr)

with the NetworkElement defined as (only showing the __enter__ and __exit__ )

class NetworkElement(object)

  def __enter__(self):
    return self
    
  def __exit__(self, exctype, excinst, exctb):
     if exctype == NetworkElementError:
       logging.exception('No routing table found')
       self.oldne.cleanup('rollback')
     else:
       self.oldne.cleanup('commit')
     self.oldne.disconnect()

You can do a lot of validation, cleaning up, disconnecting and error handling inside the context manager, leaving the user of the context manager to focus on the main logic they want to implement.

Another handy use case for context managers is with testing exceptions with pytest:

import pytest


def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

You can also use context managers when mocking tests with patch:

from unittest.mock import patch

with patch('Class.method', mocked_method):
  # Run your tests
  
# once the block is over, the original method is put back

Trey Hunter has a great series of screencasts for context managers if you want to learn more.