Context manager: Error handling inside __init__ method

Question:

A bit of context

I am working with a package that allows you to calculate several things about planets (such as their speed, or position), using information stored in files. The package includes methods to load, and unload files, so its basic usage would look like this:

load(["File_1", "File_2"])

try:

    function()

finally:

    unload(["File_1", "File_2"])

As this is a textbook example of the utility of a context manager, and the package lacks one, I am writing my own.

class file_manager:

    def __init__(self, file_list) -> None:

        self.file_list = file_list

        load(self.file_list)

        return None

    def __enter__(self) -> None:

        return None

    def __exit__(self, exc_type, exc_value, traceback) -> None:

        unload(self.file_list)

        return None

With the new context manager, the previous example can be rewritten as follows:

with file_manager(["File_1", "File_2"]):

    function()

and the __exit__ method guarantees that files will still be unloaded if function raises an error.

My problem

The load function loads files one by one, without first checking if all of them are available. As a result, if File_1 exists, but File_2 doesn’t, File_1 will be loaded, and an exception will be raised while loading File_2. According to python documentation:

The with statement guarantees that if the __enter__() method returns without an error, then __exit__() will always be called.

Therefore, in the previous case, the execution of the program will end without File_2 being unloaded.

What am I looking for

I can obviously fix this by using a try...except clause inside the __init__() method:

def __init__(self, file_list) -> None:

    self.file_list = file_list

    try:

        load(self.file_list)

    except FileDoesNotExistError:

        self.__exit__(FileDoesNotExistError, False, None)

but I want to know if this is the proper way to solve this problem. For example, in Cython, classes have a __dealloc__() method, which is guaranteed to run, no matter what type of exception occurs.

Asked By: alfonsoSR

||

Answers:

You can wrap your original code using contextlib.contextmanager.

from contextlib import contextmanager

@contextmanager
def file_manager(file_list):
    try:
        load(file_list)
        yield None  # after this the code inside the with block is executed 
    finally:
        # this is called when the with block has finished
        # or when load raises an exception
        unload(file_list)

and use it like

with file_manager(["File_1", "File_2"]):
    function()
Answered By: cherrywoods
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.