Context Managers in Python - Interview Questions and Answers

A context manager is an object that properly manages resources using with statements, ensuring proper setup and cleanup.

A context manager implements __enter__() (setup) and __exit__() (cleanup) methods, ensuring proper resource handling.

They ensure that resources like files, database connections, and network sockets are closed automatically, reducing resource leaks.

You use a context manager with the with statement:

with open("file.txt", "r") as file:
    content = file.read()

This ensures the file is closed after reading.

The __exit__() method of the context manager is still executed, handling cleanup even if an exception occurs.

Yes, but it's not recommended because you must manually call __enter__() and __exit__() methods.

You can use multiple context managers like this:

with open("file1.txt") as f1, open("file2.txt") as f2:
    data = f1.read() + f2.read()

Both files are properly closed after execution.

By defining a class with __enter__() and __exit__() methods.

It initializes the resource and returns the resource object if needed.

It handles cleanup, closes resources, and optionally suppresses exceptions.

Yes, by returning True from __exit__().

class IgnoreErrors:
    def __enter__(self):
        pass
    def __exit__(self, exc_type, exc_value, traceback):
        return True  # Suppresses exceptions

with IgnoreErrors():
    1 / 0  # No error raised

 

The exception is propagated normally.

A built-in module that provides utilities for working with context managers.

from contextlib import contextmanager

@contextmanager
def my_context():
    print("Entering")
    yield
    print("Exiting")

with my_context():
    print("Inside context")

 

It requires less boilerplate code and is easier to read.

  • File handling
  • Database connections
  • Lock management in multithreading

Yes, many database libraries implement context managers for managing connections.

import sqlite3
with sqlite3.connect("mydb.db") as conn:
    conn.execute("CREATE TABLE test (id INT)")

 

Yes, context managers can be nested.

A context manager that can be entered multiple times safely.

 

By implementing logic inside __exit__() to log or suppress exceptions.

Yes, it can return an initialized resource.

 

import socket

class SocketManager:
    def __enter__(self):
        self.sock = socket.socket()
        self.sock.connect(("example.com", 80))
        return self.sock

    def __exit__(self, exc_type, exc_value, traceback):
        self.sock.close()

with SocketManager() as sock:
    sock.send(b"GET / HTTP/1.1\r\n")

 

The __exit__() method is not called, and the exception propagates.

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Elapsed time: {time.time() - self.start}")

with Timer():
    time.sleep(1)

 

Yes, but you must handle cleanup inside __exit__().

By using locks inside __enter__() and __exit__().

No, it must have only one yield.

A placeholder for a context manager that does nothing.

By committing or rolling back in __exit__().

The context manager exits immediately without executing the with block.

Yes, to acquire and release locks safely.

A flexible way to manage multiple context managers dynamically.

A context manager that provides a single instance across executions.

Yes, using async def __aenter__() and async def __aexit__().

class AsyncCM:
    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        pass

async with AsyncCM() as cm:
    pass

 

It will override the original exception unless handled.

By capturing the exception inside __exit__() and logging it.

Yes, by explicitly raising it again inside __exit__().

Yes, they can manage session handling in requests.

By using a with statement to guarantee __exit__() execution.

  • Both __exit__() and finally ensure cleanup, but __exit__() is specific to context managers, while finally is a general exception handling construct.
  • Example using finally:
try:
    file = open("file.txt", "r")
    data = file.read()
finally:
    file.close()  # Ensures cleanup even if an error occurs

Example using a context manager (__exit__())

with open("file.txt", "r") as file:
    data = file.read()  # `file.close()` is handled by `__exit__()`

 

Yes, you can use a context manager to measure execution time:

import time  

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Elapsed time: {time.time() - self.start}")

with Timer():
    time.sleep(1)  # Measure execution time

 

  • If __exit__() modifies exc_value, it can suppress or alter the exception.
  • Example:
class ModifyException:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print(f"Original Exception: {exc_value}")
            exc_value.args = ("Modified Error Message",)  # Modifying exception
            return False  # Exception will still propagate

with ModifyException():
    raise ValueError("Original Error")  

Output:

Original Exception: Original Error
Traceback (most recent call last):
ValueError: Modified Error Message

 

Yes, context managers are commonly used for mocking in unit tests.

Example using unittest.mock.patch

from unittest.mock import patch

def get_data():
    return "Real Data"

with patch("__main__.get_data", return_value="Mocked Data"):
    print(get_data())  # Output: Mocked Data

The patch() context manager temporarily replaces get_data() with a mocked return value.

  • A recursive context manager allows nested usage within the same instance.
  • Example
class RecursiveCM:
    def __init__(self):
        self.depth = 0

    def __enter__(self):
        self.depth += 1
        print(f"Entering level {self.depth}")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Exiting level {self.depth}")
        self.depth -= 1

cm = RecursiveCM()

with cm:
    with cm:
        with cm:
            print("Inside nested context")

Output:

Entering level 1
Entering level 2
Entering level 3
Inside nested context
Exiting level 3
Exiting level 2
Exiting level 1

 

  • contextlib.suppress() is a concise way to ignore specific exceptions inside a with statement.
  • Example using suppress()
from contextlib import suppress

with suppress(ZeroDivisionError):
    print(1 / 0)  # No exception raised

Equivalent try-except block:

try:
    print(1 / 0)
except ZeroDivisionError:
    pass  # Silently suppresses the error

 

  • A nested context manager manages multiple resources inside a single with statement.
  • Example:
with open("file1.txt") as f1, open("file2.txt") as f2:
    data = f1.read() + f2.read()

Both files are properly closed after execution.

  • Yes, async context managers are used with async with.
  • Example with aiomysql
import aiomysql
import asyncio

async def main():
    async with aiomysql.connect(host='localhost', user='root', password='pass', db='test') as conn:
        async with conn.cursor() as cur:
            await cur.execute("SELECT * FROM users")
            print(await cur.fetchall())

asyncio.run(main())
  • async with ensures proper cleanup of database connections.

  • The tempfile module provides built-in context managers for temporary files.
  • Example:
import tempfile

with tempfile.NamedTemporaryFile(delete=False) as temp:
    temp.write(b"Temporary data")
    print("Temporary file:", temp.name)

The file is automatically deleted when the context exits unless delete=False is set

  • Normally, a new instance is used for each with block, but state can be stored in class attributes.
  • Example:
class StatefulCM:
    def __init__(self):
        self.count = 0

    def __enter__(self):
        self.count += 1
        print(f"Entering {self.count} times")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Exiting {self.count} times")

cm = StatefulCM()

with cm:
    with cm:
        with cm:
            pass

Output:

Entering 1 times
Entering 2 times
Entering 3 times
Exiting 3 times
Exiting 2 times
Exiting 1 times

 

Share   Share