Python language basics 85: file I/O resource management with try-finally
March 6, 2016 Leave a comment
Introduction
In the previous post we started looking into file operations in Python. We discussed various basic terms such as the file access mode and character encoding. We also went through some very basic code to write to a text file. We saw that the code wasn’t well designed. It is error-prone and not very reliable.
In this post we’ll present a way to make the code better with a traditional try-except-finally block.
Try-finally
We looked at exception handling before in this series so I’ll not go through it again. We can enclose the file handling code in a try-finally block to make sure that open resources are closed regardless of the actual code execution.
In its simplest form we can have the following:
try: fullpath = os.path.join('C:/', 'tmp', 'source.txt') file = open(fullpath, mode='wt', encoding='UTF-8') file.write('This is some content, ') file.write('this is some more content, ') file.write('good bye for now.') finally: print("Closing the file...") file.close()
Note how we put the filehandle.close function in the finally block. That’s a very basic form of resource management to prevent memory leaks. This prints Closing the file… in the output window regardless of the outcome of the try block. If I provide a non-existent folder path, such as c:\nosuchfolder then I get the following result:
Closing the file…
FileNotFoundError: [Errno 2] No such file or directory: ‘C:/nosuchfolder\\source.txt’
However, there’s one more error:
UnboundLocalError: local variable ‘file’ referenced before assignment
The reason is that the open method threw an exception BEFORE the ‘file’ variable could have been initialised. The moment we reach the file.close part the variable file is null, therefore we cannot call the close method on it. In that case we’re actually safe as the file handle wasn’t open in the first place.
We can of course catch that error and handle it gracefully. We can also add exception handling in case an error is thrown in the try block. The most common exception type we can encounter during file operations is the IOError:
try: fullpath = os.path.join('C:/', 'nosuchfolder', 'source.txt') file = open(fullpath, mode='wt', encoding='UTF-8') file.write('This is some content, ') file.write('this is some more content, ') file.write('good bye for now.') except IOError as e: print('Error: {0}'.format(e)) finally: print("Closing the file...") try: file.close() except UnboundLocalError as ule: print('Could not close file: {0}'.format(ule))
The above code produces the following output:
Error: [Errno 2] No such file or directory: ‘C:/nosuchfolder\\source.txt’
Closing the file…
Could not close file: local variable ‘file’ referenced before assignment
The above code is better than what it looked like previously. It handles errors and takes care of cleaning up the resources after it. However, we can improve it further with a ‘with’ block which takes care of closing the resources for us. We’ll see how it works in the next part.
Read all Python-related posts on this blog here.