I usually design my Python programs so that if a program needs to read or write to a file, the functions will take a filename argument that can be either a path string or a file-like object already open for reading / writing.
This has the great advantage of making tests easier to write. Instead of having to create dummy temporary files on disk I can wrap strings in
StringIO() and pass that instead.
But the disadvantage is I then have a bit of boiler-plate at the top of the function:
def read_something(filename): # Tedious but not heinous boiler-plate if isinstance(filename, basestring): filename = open(filename) return filename.read()
The other drawback is that code doesn’t close the file it opened. You could have
filename.close() before returning but that will also close file-like objects that were passed in, which may not be what the caller wants. I think the decision whether to close the file belongs to the caller when the argument is a file-like object.
You could set a flag when opening the file, and then close the file afterwards if the flag is set, but that is yet more boiler-plate and quite ugly.
So here is a context manager which behaves like
open(). If the argument is a string it handles opening and closing the file cleanly. If the argument is anything else then it just reads the contents.
class open_filename(object): """Context manager that opens a filename and closes it on exit, but does nothing for file-like objects. """ def __init__(self, filename, *args, **kwargs): self.closing = kwargs.pop('closing', False) if isinstance(filename, basestring): self.fh = open(filename, *args, **kwargs) self.closing = True else: self.fh = filename def __enter__(self): return self.fh def __exit__(self, exc_type, exc_val, exc_tb): if self.closing: self.fh.close() return False
And then you use it like this:
from io import StringIO file1 = StringIO(u'The quick brown fox...') file2 = 'The quick brown fox' with open_filename(file1) as fh1, open_filename(file2) as fh2: foo, bar = fh1.read(), fh2.read()
If you always want the file to be closed on leaving the block you use the closing keyword argument set to
True (the default of
False means the file will only be closed if it was opened by the context manager).
file1 = StringIO(u'...jumps over the lazy dog.') assert file1.closed == False with open_filename(file1, closing=True) as fh: foo = fh.read() assert file1.closed == True
Today is my brother’s birthday. If I had asked him what he wanted for a present I am pretty certain he would have asked for a blog post about closing files in a computer programming language.