Context managers were introduced in PEP 343.
They’re an incredibly useful construct for patterns of code that involve any sort of “cleanup” at the end of an execution of some code block.
In this article, I’m going to show the specification of context managers as laid out in PEP 343 in terms of actual code.
According to the spec, the following with
syntax:
with EXPR as VAR:
BLOCK
Translates to:
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
Here’s some code to demonstrate this equivalence.
First, this is a custom context manager I wrote for handling files opened for reading:
class MyFileContextManager(object):
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'r')
return self.file
def __exit__(self, *exc_info):
self.file.close()
Using the with EXPR as VAR
syntax:
with MyFileContextManager("http-request.py") as f:
print(f.read())
And the “unpacked” form:
import sys
mgr = MyFileContextManager('http-request.py')
exit = type(mgr).__exit__
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
f = value
print(f.read())
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
Looking at actual code, we can see how much work is done behind the scenes when using with
. In a nutshell, it:
- Invokes
__enter__
on the context manager object - Executes the block of code nested inside the context manager
with
- Invokes
__exit__
on the context manager object
If you look more closely, this implementation has many implications. For example, if you implement a custom context manager like I did and return a truthy value for __exit__
, the originating exception from the code block can get swallowed.
It’s not a ton of code, but the with
syntax is another one of those language constructs that make Python a joy to work with.