lightsoffon1class Lights:2 def __enter__(self):3 print("lights on")4 return "room"5 def __exit__(self, typ, err, tb):6 print("lights off")7 return False89with Lights() as r:10 print("in the", r)11 1 / 0 return Truefrom contextlib import contextmanager@contextmanagerdef lights(): print("lights on") try: yield "room" finally: print("lights off")with lights() as r:Lights()__enter__()lights on'room'in the roomZeroDivisionErrorlights offZeroDivisionError
Code card: lights.py; the highlighted line is the one runningLamp: the resource, on between the set-up and the clean-upTerminal: where print() and the traceback writeMemory boxes: the manager object and the variable rlights.py defines a context manager, Lights, and uses it in a with statement whose block fails with ZeroDivisionError. Each step lights the parts it is about. with makes the manager and calls its __enter__ method: the lamp turns on and __enter__'s return value goes into r. When line 11 fails, with hands the error to __exit__, which turns the lamp off before the error goes on to the terminal as a traceback. Returning True from __exit__ swallows the error, and @contextmanager writes the same manager as a generator.

Setting up

  1. Some things must be undone after use: close a file, release a lock, turn the lights off. A context manager is an object that does the set-up and the clean-up, and the with statement makes sure the clean-up runs.

Cleaning up

Variations

Try it yourself

;; step 1/11
# without with, the clean-up is easy to miss:
f = open("notes.txt", "w")
f.write("hi")
f.close()        # skipped if write() fails

# with closes the file, whatever happens:
with open("notes.txt", "w") as f:
    f.write("hi")

__exit__ always gets three arguments after self. When the block ends without an error all three are None; on an error they are the exception's type, the exception and its traceback. The figure shortens the traceback. The real one shows the full path to lights.py, then line 11 itself with ~~^~~ marks under the division.