ProgrammingBackend Python Developer

What are context managers in Python, how are they implemented through the __enter__ and __exit__ protocols, and what are the practical advantages of such a construct?

Pass interviews with Hintsage AI assistant

Answer.

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:

  • Automation and safety of releasing external resources
  • Universal interface through enter/exit suitable for files, transactions, and locks
  • Allows error handling without repeating try-finally code

Tricky questions.

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: ...

Typical mistakes and anti-patterns

  • Opening/closing the resource outside of enter/exit
  • Caught exception with return True from exit — completely suppresses the error
  • Incorrectly handled case of re-entering the context

Real-life example

Negative case

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:

  • Minimal code, without "extra" structure

Cons:

  • Resource leaks, program breaking on long runs, unexpected crashes

Positive case

Using with open() everywhere as a standard (or custom managed resource), the context manager correctly implements resource freeing.

Pros:

  • Explicit management of resource lifecycle
  • Protection against errors during exceptions

Cons:

  • Need to design the class/function in advance as a context manager; complicates the code structure for beginners