Reputation: 303
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.
Upvotes: 5
Views: 688
Reputation: 1361
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()
Upvotes: 4