Context managers allow for the automation and control of code blocks execution with guaranteed finalization of resources — for example, automatically closing files, releasing transaction managers in databases, or locking/unlocking threads. This paradigm was introduced in standard Python for safe and convenient management of external resources without the need to manually track the final stage of execution.
History:
Before the introduction of context managers, it was necessary to explicitly call close()/release() methods to free resources, which could lead to errors if exceptions occurred. With the advent of the with ... as ...: construct, Python allowed defining the "scope of the resource" explicitly, automatically calling methods to initialize and finalize work with it.
Problem:
Manually managing the "closing" of a resource is dangerous — if you forget to call close() (or release()), resources will remain occupied until the process ends or may even hang forever. This is especially critical when working with files, network connections, and database transactions.
Solution:
Context managers are implemented through the magic methods enter() and exit(). Upon entering the with block, Python calls enter, and upon exiting — exit, even if an exception occurred within the block.
Example code:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # You can suppress the exception with return True, usually return False with ManagedFile('demo.txt') as f: f.write('Hello, world!\n') # the file is guaranteed to be closed
Key features:
Can the same instance of a context manager be used consecutively in different with statements?
No: typically, the resource is opened in enter, released in exit, and the object becomes invalid for reuse. It's better to create a new object for each with block.
What to do if you need to use multiple resources in one with?
You can separate variables with a comma:
with open('a.txt') as fa, open('b.txt') as fb: ...
or use contextlib.ExitStack for complex cases.
What is the difference between writing a context manager as a class with enter/exit and as a generator with the @contextmanager decorator?
The @contextmanager decorator from the contextlib module allows you to implement a manager more simply, using yield:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
A developer opens a file with open(), writes to it, and forgets to call close() — the file may remain open (especially on errors), and data is not written.
Pros:
Cons:
Using with open() everywhere as a standard (or custom managed resource), the context manager correctly implements resource freeing.
Pros:
Cons: