with and the protocol
A context manager is an object that knows how to set up and tear down around a block of code. The with statement uses two methods: __enter__ runs at the start of the block, __exit__ runs at the end, even if the block raises an exception.
The canonical example is a file. with open(path) as f: ... opens the file at the start of the block and closes it at the end. The with statement promises the close runs no matter what happens inside.
Before with, the equivalent code was try / finally with explicit cleanup. The with form is shorter, harder to forget, and clearer about which lines are inside the resource and which are not. Reach for it whenever cleanup is part of using a thing.