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

# with invokes the context manager
# which takes care of closing handle promptly
with open('csv_file_path', 'w') as file_obj: 
    file_obj.write('This is a trial line !') 

The above is equivalent to

file_obj = open('csv_file_path', 'w') 
file_obj.write('This is a trial line !') 
file_obj.close() 

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.
Please note below:

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']

The file object returned by open() has the methods __enter__, __exit__ and __init__ and hence behaves as a resource the context of which can be managed.

How to write a simple context manager?

This code example shows writing a custom context manager to handle opening and closing a file object.

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)

Output:

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?

Those are passed on for special handling of situations based on exception type, value and traceback should any exceptions are encountered after open() and before close() of the resource. One exception can be: even as a file is being written disk space runs out. The above example does not show any handling of the exceptions in the __exit__() method though. But exception handling logic can be written in anticipation using ex_type, ex_value and ex_traceback

But then, I feel lazy...

It can be cumbersome to create context managers specially custom __enter__ and __exit__ s for every type of resource you try to acquire. Python has contextlib for rescue.

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.

And hence, do go for it...

Comments

Popular Posts