A case for Context Managers
To begin, the idea of Context can be rather confusing in the manner it is used in different programming languages. The difference can be seen in both how context is created and used thereafter in its lifecycle.
What is context?
To put simply we can describe context as a resource or set of resources with special attention to how the same is interpreted after creation. To clarify, let's give a few examples:
Perl:
# localtime() is an in-built routine to produce current-time
# Below shows how context is open to different interpretations
# scalar context
$scalar_time = localtime();
# list context
@list_time = localtime();
Python:
A process handle, file handle (simple file object, db connection object, thread etc)
Java:
In Java context is related to Facade design pattern wherein underlying environment resource and implementations are abstracted from the user and a mere 'Context' is made available to the user to be used as a single API endpoint for all the resources needed for a particular purpose eg. ServletContext(Servlets), ApplicationContext(Java Spring Framework) etc. Java has an Context interface to guide implementing contexts.
What is context management?
Contexts are inherently resources and as in the real world, they are limited in programming world too. An optimized program will use constructs to release resources as soon as it is no longer required for compute or storage. You can use the garbage collection analogy but garbage collection is done without the user or developer triggering it whereas context management is in the realm of the 'Application' or the 'Program' itself and hence, is controllable by the user. Following is given an example of context management:
file_obj = open("read.csv", "r")
file_content = file_obj.read()
# Now the file handle is closed and hence the context is released
file_content.close()
# If you don't do the above ie close() the 'context' will remained acquired
# until the program goes out of context (I am again taking some liberty to add # confusion :-). On a serious note, the file handle will remain open until the
# executing block goes out of scope)
What are Context Managers?
Context managers are high level constructs provided to oversee and manage context life-cycle so that contexts do not linger after their use is over.
Though all programming languages have ways of releasing resources, it is at the mercy of the developer whether and when context is released. As it is said 'To err is human, to forgive is divine', programming languages are never forgiving, and often very sinister sucking the life-blood out with erratic runtime behaviour. Think about nasty bugs 👹...
To cut it short, lazy and|or careless developers need a safeguard against not caring to manage the lifecycle of contexts and Context Managers do just that.
Examples, examples...
I will only cite Python here:
The easy peasy
The above is equivalent to
The markings of a context manager
How the combination of a keyword 'with' and a call to 'open()' invoked context management?
Well, two points to note here:
- Context managers in Python need to be invoked with 'with'
- The object invoked itself has to be a of a class which is has some particular ways about it.
file_obj = open("write.csv", "w")
print(dir(file_obj))
# output below
"""
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']
How to write a simple context manager?
class FileCxtManager():
def __init__(self, file_path, mode):
print(f"Initiating for {file_path} in mode {mode}")
self._filename = file_path
self._mode = mode
self._file = None
def __enter__(self):
self._file = open(self._filename, self._mode)
print(f"Opened {self._filename} in mode {self._mode}")
return self._file
def __exit__(self, ex_type, ex_value, ex_traceback):
self._file.close()
print(f"Closed {self._filename}")
with FileCxtManager('write.csv', 'w') as file_in:
file_in.write('Test, Test, Test, Test')
with FileCxtManager('write.csv', 'r') as file_out:
for line in file_out.readlines():
print(line)
Initiating for write.csv in mode w
Opened write.csv in mode w
Closed write.csv
Initiating for write.csv in mode r
Opened write.csv in mode r
Test, Test, Test, Test
Closed write.csv
What of the __exit__() ex_type, .... parameters?
But then, I feel lazy...
from contextlib import contextmanager
@contextmanager
def open_file(filename, mode):
fhandle = open(filename, mode)
try:
yield fhandle
finally:
fhandle.close()
with open_file("write.csv", "w") as fwrite:
fwrite.write("col1, col2, col3, col4")
with open_file("write.csv", "r") as fread:
for line in fread:
print(line)
To summarize
- Context managers safeguard against programming ommissions.
- Reduce number of lines of code.
- Help write cleaner looking code.
- Isolate resource handling as a separate concern.
Comments