Context Manager in Python
In any programming language, the usage of resources like file operations or database connections is very common. But these resources are limited in supply. Therefore, the main problem lies in making sure to release these resources after usage. If they are not released then it will lead to resource leakage and may cause the system to either slow down or crash.
Python’s context managers provide a neat way to automatically set up and clean up resources, ensuring they’re properly managed even if errors occur.
Using the with Statement for File Handling
The simplest way to manage a file resource is using the with keyword:
with open("test.txt") as f:
data = f.read()
This ensures the file is automatically closed once the block is exited, even if an error occurs.
What Happens Without Proper Closing?
If files aren’t closed, you can run out of available file descriptors. For example:
file_descriptors = []
for x in range(100000):
file_descriptors.append(open('test.txt', 'w'))
This will raise:
Traceback (most recent call last):
File "context.py", line 3, in
OSError: [Errno 24] Too many open files: 'test.txt'
Because too many files remain open, exhausting system resource
Why Use Context Managers?
In complex programs, especially those with multiple exit points or exceptions, manually closing files or connections everywhere is error-prone. Context managers automate this cleanup using the with keyword.
Creating a Custom Context Manager Class
A class-based context manager needs two methods:
- __enter__(): sets up the resource and returns it.
- __exit__(): cleans up the resource (e.g., closes a file).
Example:
class ContextManager:
def __init__(self):
print('init method called')
def __enter__(self):
print('enter method called')
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
print('exit method called')
with ContextManager() as manager:
print('with statement block')
Output:
init method called
enter method called
with statement block
exit method called
The above sequence shows how Python initializes the object, enters the context, runs the block, and then exits while cleaning up.
File Management Using Context Manager
Let's apply the above concept to create a class that helps in file resource management. The FileManager class helps in opening a file, writing/reading contents, and then closing it.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
self.file.close()
with FileManager('test.txt', 'w') as f:
f.write('Test')
print(f.closed)
Output:
True
Explanation:
- __enter__() opens the file and returns it.
- Inside the with block, you can use the file object.
- __exit__() ensures the file is closed automatically.
- print(f.closed) confirms the file is closed.
Database Connection Management with Context Manager
Let's create a simple database connection management system. The number of database connections that can be opened at a time is also limited(just like file descriptors). Therefore context managers are helpful in managing connections to the database as there could be chances that the programmer may forget to close the connection.
from pymongo import MongoClient
class MongoDBConnectionManager:
def __init__(self, hostname, port):
self.hostname = hostname
self.port = port
self.connection = None
def __enter__(self):
self.connection = MongoClient(self.hostname, self.port)
return self.connection
def __exit__(self, exc_type, exc_value, exc_traceback):
self.connection.close()
with MongoDBConnectionManager('localhost', 27017) as mongo:
collection = mongo.SampleDb.test
data = collection.find_one({'_id': 1})
print(data.get('name'))
Explanation:
- __enter__() opens the MongoDB connection.
- mongo inside the with block is the client object.
- You can perform database operations safely.
- __exit__() closes the connection automatically.