| PolarSPARC |
Primer on Python Context Manager
| Bhaskar S | 08/31/2025 |
Overview
When developing Enterprise grade applications with Python, one needs to properly manage references external resources such as files, databases, locks, or network connections, such that those references are properly closed when they are no longer needed. This is where the Python Context Manager comes in handy.
A Context Manager in Python is an object that defines a temporary context surrounding a block of code, which involves a critical resource that needs to be automatically setup on entry and cleaned up on exit. The context manager is activated using the with statement and aims to simplify resource management even in the event of runtime exceptions.
The following simple Python script demonstrates how one would manage a file resource in a proper way:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-1')
# Main method
def main():
f_name = '/tmp/sample-1.log'
file = None
try:
file = open(f_name, 'r')
logger.info('Opened file %s', f_name)
except IOError as e:
logger.error(e)
finally:
if file is not None:
file.close()
logger.info('Closed file %s', f_name)
if __name__ == '__main__':
main()
To run the Python script sample-1.py, execute the following command:
$ python3 sample-1.py
The following would be a typical output:
2025-08-30 22:18:03 | INFO -> File name /tmp/sample-1.log 2025-08-30 22:18:03 | ERROR -> [Errno 2] No such file or directory: '/tmp/sample-1.log'
One could achieve the same result in a more elegant and succinct way using a context manager as shown in the following Python script:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-2')
# Main method
def main():
f_name = '/tmp/sample-2.log'
with open(f_name, 'r') as file:
logger.info(f'File {f_name} opened')
if __name__ == '__main__':
main()
To run the Python script sample-2.py, execute the following command:
$ python3 sample-2.py
The following would be a typical output:
2025-08-30 22:18:26 | INFO -> File name /tmp/sample-2.log
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-2.py", line 26, in <module>
main()
File "/home/polarsparc/python/ContextManager/sample-2.py", line 22, in main
with open(f_name, 'r') as file:
^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/sample-2.log'
Under the hood, the context management protocol implements the following two special methods:
__enter__() :: this method is invoked when the with keyword is encountered to enter the runtime context and is typically used for setup of the managed resource
__exit__() :: this method is invoked when the execution exits the with code block and is typically used for cleanup of the managed resource
The following Python script shows the ingredients of a Context Manager class under the hood:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-3')
# Test context manager class
class TestContextManager(object):
def __init__(self, name: str='Test'):
self.name = name
logger.info(f'Initializing context for {self.name}')
def __enter__(self):
logger.info(f'Entering context for {self.name}')
return self
def __exit__(self, ex_type, ex_value, ex_traceback):
logger.info(f'Exiting context for {self.name}')
logger.info(f'Exiting context for {self.name} with type: {ex_type}')
logger.info(f'Exiting context for {self.name} with value: {ex_value}')
logger.info(f'Exiting context for {self.name} with traceback: {ex_traceback}')
def action(self, flag: bool=False):
logger.info(f'Performing action in the context for {self.name}')
if flag:
logger.info(f'Flag is set to True, raising exception')
raise Exception('Test Exception')
# Main method
def main():
with TestContextManager(name='Good') as tc1:
tc1.action()
with TestContextManager(name='Bad') as tc2:
tc2.action(flag=True)
if __name__ == '__main__':
main()
One aspect of the sample-3.py from the above needs a little explanation.
The __exit__() method takes in additional parameters which are ONLY applicable in an exception situation and are as follows:
ex_type :: indicates the class type of the exception
ex_value :: indicates the exception message
ex_traceback :: indicates all trace information about the exception
To run the Python script sample-3.py, execute the following command:
$ python3 sample-3.py
The following would be a typical output:
2025-08-31 08:08:22 | INFO -> Initializing context for Good
2025-08-31 08:08:22 | INFO -> Entering context for Good
2025-08-31 08:08:22 | INFO -> Performing action in the context for Good
2025-08-31 08:08:22 | INFO -> Exiting context for Good
2025-08-31 08:08:22 | INFO -> Exiting context for Good with type: None
2025-08-31 08:08:22 | INFO -> Exiting context for Good with value: None
2025-08-31 08:08:22 | INFO -> Exiting context for Good with traceback: None
2025-08-31 08:08:22 | INFO -> Initializing context for Bad
2025-08-31 08:08:22 | INFO -> Entering context for Bad
2025-08-31 08:08:22 | INFO -> Performing action in the context for Bad
2025-08-31 08:08:22 | INFO -> Flag is set to True, raising exception
2025-08-31 08:08:22 | INFO -> Exiting context for Bad
2025-08-31 08:08:22 | INFO -> Exiting context for Bad with type: <class 'Exception'>
2025-08-31 08:08:22 | INFO -> Exiting context for Bad with value: Test Exception
2025-08-31 08:08:22 | INFO -> Exiting context for Bad with traceback: <traceback object at 0x7ceb9ee3fd80>
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-3.py", line 50, in <module>
main()
File "/home/polarsparc/python/ContextManager/sample-3.py", line 47, in main
tc2.action(flag=True)
File "/home/polarsparc/python/ContextManager/sample-3.py", line 38, in action
raise Exception('Test Exception')
Exception: Test Exception
The built-in Python module contextlib provides a function decorator as well as a variety of utility class(es) for context management, which can simplify application development of building and maintaining any custom context manager(s).
The decorator contextlib.contextmanager allows one to convert a generator function into a Context Manager.
The following Python script demonstrates the use of the contextmanager decorator:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-4')
# Context manager using decorator
from contextlib import contextmanager
@contextmanager
def simple_text_context():
logger.info('Entered simple text context ...')
try:
yield 'Simple hello world!'
except RuntimeError as re:
logger.error(f'Exception occurred: {re}')
finally:
logger.info('Exiting simple text context ...')
# Main method
def main():
with simple_text_context() as value:
logger.info(f'Value: {value}')
with simple_text_context() as value:
raise RuntimeError('Simulated Runtime Error')
with simple_text_context() as value:
raise ValueError('Simulated Value Error')
if __name__ == '__main__':
main()
To run the Python script sample-4.py, execute the following command:
$ python3 sample-4.py
The following would be a typical output:
2025-08-31 09:03:19 | INFO -> Entered simple text context ...
2025-08-31 09:03:19 | INFO -> Value: Simple hello world!
2025-08-31 09:03:19 | INFO -> Exiting simple text context ...
2025-08-31 09:03:19 | INFO -> Entered simple text context ...
2025-08-31 09:03:19 | ERROR -> Exception occurred: Simulated Runtime Error
2025-08-31 09:03:19 | INFO -> Exiting simple text context ...
2025-08-31 09:03:19 | INFO -> Entered simple text context ...
2025-08-31 09:03:19 | INFO -> Exiting simple text context ...
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-4.py", line 44, in <module>
main()
File "/home/polarsparc/python/ContextManager/sample-4.py", line 41, in main
raise ValueError('Simulated Value Error')
ValueError: Simulated Value Error
The following Python script demonstrates a real world usage of the contextmanager decorator:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-5')
# Context manager using decorator
from contextlib import contextmanager
@contextmanager
def write_file_context(name):
logger.info(f'Entered write file context with {name}')
file = None
try:
logger.info(f'Opening file {name}')
file = open(name, 'w')
yield file
except RuntimeError as re:
logger.error(f'Exception occurred: {re}')
finally:
if file is not None:
logger.info(f'Closing file {name}')
file.close()
logger.info(f'Exiting write file context with {name}')
# Main method
def main():
f_name = '/tmp/test.log'
with write_file_context(f_name) as wf:
wf.write('Line - 1: Hello !!\n')
wf.write('Line - 2: Hola !!\n')
if __name__ == '__main__':
main()
To run the Python script sample-5.py, execute the following command:
$ python3 sample-5.py
The following would be a typical output:
2025-08-31 12:13:17 | INFO -> Entered write file context with /tmp/test.log 2025-08-31 12:13:17 | INFO -> Opening file /tmp/test.log 2025-08-31 12:13:17 | INFO -> Closing file /tmp/test.log 2025-08-31 12:13:17 | INFO -> Exiting write file context with /tmp/test.log
The following Python script demonstrates the support for redirecting the stdout to a different stream using the contextlib.redirect_stdout utility context manager:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import contextlib
import io
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-6')
# Stdout redirection context manager
# Main method
def main(io_stream):
with contextlib.redirect_stdout(io_stream):
print('Hello world !!!')
if __name__ == '__main__':
txt_stream = io.StringIO()
logger.info('Redirecting stdout to txt stream')
main(txt_stream)
logger.info('Done with redirecting stdout to txt stream')
logger.info(f'Value in txt stream: {txt_stream.getvalue()}')
To run the Python script sample-6.py, execute the following command:
$ python3 sample-6.py
The following would be a typical output:
2025-08-31 13:15:34 | INFO -> Redirecting stdout to txt stream 2025-08-31 13:15:34 | INFO -> Done with redirecting stdout to txt stream 2025-08-31 13:15:34 | INFO -> Value in txt stream: Hello world !!!
There are situations when one has to deal with two or more external resources and they need to be managed appropriately. This is where the contextlib.ExitStack utility class comes in handy.
The following the Python script demonstrates how one can manage two or more resources using the context management protocol in a clean and efficient manner:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-7')
# Context manager using utility class
from contextlib import ExitStack
# Main method
def main():
f_name_1 = '/tmp/f1.txt'
f_name_2 = '/tmp/f2.txt'
with ExitStack() as stack:
logger.info('Ready to write to file %s', f_name_1)
f1 = stack.enter_context(open(f_name_1, 'w'))
logger.info('Ready to read from file %s', f_name_2)
f2 = stack.enter_context(open(f_name_2, 'r'))
logger.info('Ready to perform operation on both %s and %s', f_name_1, f_name_2)
if __name__ == '__main__':
main()
If the second file fails, the first file will be properly closed.
To run the Python script sample-7.py, execute the following command:
$ python3 sample-7.py
The following would be a typical output:
2025-08-31 17:58:40 | INFO -> Ready to write to file /tmp/f1.txt
2025-08-31 17:58:40 | INFO -> Ready to read from file /tmp/f2.txt
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-7.py", line 34, in <module>
main()
File "/home/polarsparc/python/ContextManager/sample-7.py", line 30, in main
f2 = stack.enter_context(open(f_name_2, 'r'))
^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/f2.txt
Until now, we explored the context manager protocol for synchronous use-cases. The context manager protocol is also valid for async cases.
The async context management protocol implements the following two special methods:
__aenter__() :: this method is invoked when the async with keyword is encountered to enter the runtime context and is typically used for setup of the managed resource
__aexit__() :: this method is invoked when the execution exits the async with code block and is typically used for cleanup of the managed resource
The following Python script shows the ingredients of an async context manager class under the hood:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import asyncio
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-8')
# Test async context manager class
class AsyncTestContextManager(object):
def __init__(self, name: str='AsyncTest'):
self.name = name
logger.info(f'Initializing async context for {self.name}')
async def __aenter__(self):
logger.info(f'Entering async context for {self.name}')
return self
async def __aexit__(self, ex_type, ex_value, ex_traceback):
logger.info(f'Exiting async context for {self.name}')
logger.info(f'Exiting async context for {self.name} with type: {ex_type}')
logger.info(f'Exiting async context for {self.name} with value: {ex_value}')
logger.info(f'Exiting async context for {self.name} with traceback: {ex_traceback}')
async def action(self, flag: bool=False):
logger.info(f'Performing action in the async context for {self.name}')
if flag:
logger.info(f'Flag is set to True, raising exception')
raise Exception('AsyncTest Exception')
# Async main method
async def main():
async with AsyncTestContextManager(name='Good') as tc1:
await tc1.action()
async with AsyncTestContextManager(name='Bad') as tc2:
await tc2.action(flag=True)
if __name__ == '__main__':
asyncio.run(main())
To run the Python script sample-8.py, execute the following command:
$ python3 sample-8.py
The following would be a typical output:
2025-08-31 21:20:06 | INFO -> Initializing async context for Good
2025-08-31 21:20:06 | INFO -> Entering async context for Good
2025-08-31 21:20:06 | INFO -> Performing action in the async context for Good
2025-08-31 21:20:06 | INFO -> Exiting async context for Good
2025-08-31 21:20:06 | INFO -> Exiting async context for Good with type: None
2025-08-31 21:20:06 | INFO -> Exiting async context for Good with value: None
2025-08-31 21:20:06 | INFO -> Exiting async context for Good with traceback: None
2025-08-31 21:20:06 | INFO -> Initializing async context for Bad
2025-08-31 21:20:06 | INFO -> Entering async context for Bad
2025-08-31 21:20:06 | INFO -> Performing action in the async context for Bad
2025-08-31 21:20:06 | INFO -> Flag is set to True, raising exception
2025-08-31 21:20:06 | INFO -> Exiting async context for Bad
2025-08-31 21:20:06 | INFO -> Exiting async context for Bad with type: <class 'Exception'>
2025-08-31 21:20:06 | INFO -> Exiting async context for Bad with value: AsyncTest Exception
2025-08-31 21:20:06 | INFO -> Exiting async context for Bad with traceback: <traceback object at 0x70877e384140>
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-8.py", line 51, in <module>
asyncio.run(main())
File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "/home/polarsparc/python/ContextManager/sample-8.py", line 48, in main
await tc2.action(flag=True)
File "/home/polarsparc/python/ContextManager/sample-8.py", line 39, in action
raise Exception('AsyncTest Exception')
Exception: AsyncTest Exception
The decorator contextlib.asynccontextmanager allows one to convert an async generator function into an Async Context Manager.
The following Python script demonstrates the use of the asynccontextmanager decorator:
#
# @Author: Bhaskar S
# @Blog: https://polarsparc.github.io
# @Date: 30 Aug 2025
#
import asyncio
import logging
# Logging Configuration
logging.basicConfig(format='%(asctime)s | %(levelname)s -> %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.INFO)
# Logger
logger = logging.getLogger('Sample-9')
# Async context manager using decorator
from contextlib import asynccontextmanager
@asynccontextmanager
async def async_text_context():
logger.info('Entered async text context ...')
try:
yield 'Async hola world!'
except RuntimeError as re:
logger.error(f'Exception occurred: {re}')
finally:
logger.info('Exiting async text context ...')
# Main method
async def main():
async with async_text_context() as value:
logger.info(f'Value: {value}')
async with async_text_context() as value:
raise RuntimeError('Simulated Runtime Error')
async with async_text_context() as value:
raise ValueError('Simulated Value Error')
if __name__ == '__main__':
asyncio.run(main())
To run the Python script sample-9.py, execute the following command:
$ python3 sample-9.py
The following would be a typical output:
2025-08-31 21:37:26 | INFO -> Entered async text context ...
2025-08-31 21:37:26 | INFO -> Value: Async hola world!
2025-08-31 21:37:26 | INFO -> Exiting async text context ...
2025-08-31 21:37:26 | INFO -> Entered async text context ...
2025-08-31 21:37:26 | ERROR -> Exception occurred: Simulated Runtime Error
2025-08-31 21:37:26 | INFO -> Exiting async text context ...
2025-08-31 21:37:26 | INFO -> Entered async text context ...
2025-08-31 21:37:26 | INFO -> Exiting async text context ...
Traceback (most recent call last):
File "/home/polarsparc/python/ContextManager/sample-9.py", line 45, in <module>
asyncio.run(main())
File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "/home/polarsparc/python/ContextManager/sample-9.py", line 42, in main
raise ValueError('Simulated Value Error')
ValueError: Simulated Value Error
With this, we conclude the primer on Python Context Manager !!!
References