I have a dilemma writing a context/resource manager wrapper for a file object which passes Pylint, for use with with: Do I put the wrapped open call in __init__, or in __enter__?
Conceptually, this is a class that wraps open by accepting either a filename, the string -, or a file object (such as sys.stdin) and does 'the right thing'. In other words, if it's a filename other than -, it opens the file and manages it as a resource; otherwise, it either chooses a default file object (expected to be sys.stdin or sys.stdout) if the filename is -, or if it is a file object, uses that file object unchanged.
I see two possibilities, neither of which are working out right now: The wrapped open goes in the __init__ constructor, or it goes in the __enter__ context management method. If I put it in the __init__ constructor, which examples on SO suggest, Pylint -- which I must pass -- fails on the open with R1732: Consider using 'with' for resource-allocating operations (consider-using-with). If I put the open in the __enter__ method, Pylint is happy, but I am not sure this is the correct practice, plus the problem is, in one use case, I need the file object in order to initialize a base class (and Pylint won't let me call the base class constructor in the __enter__ method).
Some example code is in order. Here is code that opens in the constructor:
class ManagedFile:
'''Manage a file, which could be an unopened filename, could be
'-' for stdin/stdout, or could be an existing filehandle'''
def __init__(self, file_in, handle_default, open_kwargs):
''' Open a file if given a filename, or handle_default if -,
or if it's a file object already, just pass it through.
:param file_in: Valid filename, '-', or file-like object
:param handle_default: What to return if file is '-'
:param open_kwargs: Dictionary of options to pass to open() if used
'''
self.file_handle = None
self.file_in = None
if isinstance(file_in, io.IOBase):
self.file_handle = file_in
elif isinstance(file_in, str):
if file_in is None or file_in == "-":
self.file_handle = handle_default
else:
self.file_handle = open(self.file_in, **open_kwargs)
self.file_in = file_in
else:
raise TypeError('File specified must be string or file object')
def __enter__(self):
self.file_handle.__enter__()
return self.file_handle
def __exit__(self, err_type, err_value, traceback):
self.file_handle.__exit__(err_type, err_value, traceback)
self.file_handle.close()
self.file_in = None
def handle(self):
'''Return handle of file that was opened'''
return self.file_handle
And here is how I would do it with the open call in the __enter__ method:
class ManagedFile:
'''Manage a file, which could be an unopened filename, could be
'-' for stdin/stdout, or could be an existing filehandle'''
def __init__(self, file_in, handle_default, open_kwargs):
''' Open a file if given a filename, or handle_default if -,
or if it's a file object already, just pass it through.
:param file_in: Valid filename, '-', or file-like object
:param handle_default: What to return if file is '-'
:param open_kwargs: Dictionary of options to pass to open() if used
:return: Managed file object
'''
self.managed = False
self.file_handle = None
self.open_kwargs = {'mode': 'r'}
self.file_in = None
if isinstance(file_in, io.IOBase):
self.file_handle = file_in
elif isinstance(file_in, str):
if file_in is None or file_in == "-":
self.file_handle = handle_default
else:
self.file_in = file_in
self.open_kwargs = open_kwargs
self.managed = True
else:
raise TypeError('File specified must be string or file object')
def __enter__(self):
if self.managed:
self.file_handle = open(self.file_in, **self.open_kwargs)
self.file_handle.__enter__()
return self.file_handle
def __exit__(self, err_type, err_value, traceback):
self.file_handle.__exit__(err_type, err_value, traceback)
self.file_handle.close()
self.managed = False
self.file_in = None
def handle(self):
'''Return handle of file that was opened'''
return self.file_handle
I've pored over many SO questions & answers, but haven't been able to triangulate the exact answer I'm looking for, particular one that accounts for why Pylint flags an error when I ostensibly do the right thing.
Undoubtedly I've committed some Python errors in this code so any other ancillary correction would be welcome. Other ideas are also welcome but please don't get too fancy on me.
Ideally the class would itself behave as a full-fledged file object, but right now I'm focusing on something simple: Something that just manages a file handle (i.e. a reference to a regular file object). Extra gratitude if someone can provide some hints on turning it into a file object.
Python version is 3.8.10; platform is Linux.