Python Data Persistence – Exceptions

Python Data Persistence – Exceptions

Even an experienced programmer’s code does contain errors. If errors pertain to violation of language syntax, more often than not, they are detected by the interpreter (compiler in case of C++/Java) and code doesn’t execute till they are corrected.

There are times though when the code doesn’t show syntax-related errors but errors creep up after running it. What is more, sometimes code might execute without errors and some other times, its execution abruptly terminates. Clearly, some situation that arises in a running code is not tolerable to the interpreter. Such a runtime situation causing the error is called an exception.
Take a simple example of displaying the result of two numbers input by the user. The following snippet appears error-free as far as syntax error is concerned.

Example

num1=int(input('enter a number..')) 
num2 = int(input(1 enter another number..')) 
result=num1/num2 
print ('result: ' , result)

When executed, above code gives satisfactory output on most occasions, but when num2 happens to be 0, it breaks, (figure 5.3)

enter a number . . 12
enter another number . . 3
result: 4.0
enter a number . . 12
enter another number . . 0
Traceback (most recent call last) :
File "E:\python37\tmp.py", line 3, in <module> 
result =num1/num2
ZeroDivisionError: division by zero

You can see that the program terminates as soon as it encounters the error without completing the rest of the statements. Such abnormal termination may prove to be harmful in some cases.
Imagine a situation involving a file object. If such runtime error occurs after the file is opened, the abrupt end of the program will not give a chance for the file object to close properly and it may result in corruption of data in the file. Hence exceptions need to be properly handled so that program ends safely.

If we look at the class structure of builtins in Python, there is an Exception class from which a number of built-in exceptions are defined. Depending upon the cause of exception in a running program, the object representing the corresponding exception class is created. In this section, we restrict ourselves to consider file operations-related exceptions.

Python’s exception handling mechanism is implemented by the use of two keywords – try and except. Both keywords are followed by a block of statements. The try: block contains a piece of code that is likely to encounter an exception. The except block follows the try: block containing statements meant to handle the exception. The above code snippet of the division of two numbers is rewritten to use the try-catch mechanism.

Example

try:
num1=int(input('enter a number..'))
num2=int(input('enter another number..'))
result=num1/num2
print (' result: ' , result)
except:
print ("error in division")
print ("end of program")

Now there are two possibilities. As said earlier, the exception is a runtime situation largely depending upon reasons outside the program. In the code involving the division of two numbers, there is no exception if the denominator is non-zero. In such a case, try: block is executed completely, except block is bypassed and the program proceeds to subsequent statements.

If however, the denominator happens to be zero, a statement involving division produces an exception. Python interpreter abandons rest of statements in try: block and sends the program flow to except: block where exception handling statements are given. After except: block rest of unconditional statements keep on executing, (figure 5.4)

enter a number..15 
enter another number..5 
result: 3.0
end of program 
enter a number..15 
enter another number..0 
error in division 
end of program

Here, except block without any expression acts as a generic exception handler. To catch objects of a specific type of exception, the corresponding Exception class is mentioned in front of except keyword. In this case, ZeroDivisionError is raised, so it is mentioned in except statement. Also, you can use the ‘as’ keyword to receive the exception object in an argument and fetch more information about the exception.

Example

try:
num1=int(input('enter a number..')) 
num2=int(input('enter another number..')) 
result =num1/num2 
print ('result: ', result) 
except ZeroDivisionError as e: 
print ("error message",e) 
print ("end of program")

File operations are always prone to raising exceptions. What if the file you are trying to open doesn’t exist at all? What if you opened a file in ‘r’ mode but trying to write data to it? These situations will raise runtime errors (exceptions) which must be handled using the try-except mechanism to avoid damage to data in files.
FileNotFoundError is a common exception encountered. It appears when an attempt to read a non-existing file. The following code handles the error.

Example

E:\python37>python tmp.py
enter a filename, .testfile.txt
H e l l o P y t h o n
end of program
E:\python37>python tmp.py
enter filename, .nofile.txt
error message [Errno 2] No such file or directory:
' nofile . txt '
end of program

Another exception occurs frequently when you try to write data in a file opened with ‘r’ mode. Type of exception is UnsupportedOperation defined in the io module.

Example

import io
try:
f=open ( ' testfile . txt1 , ' r ')
f.write('Hello')
print (data)
except io.UnsupportedOperation as e:
print ("error message",e)
print ("end of program")

Output

E:\python37 >python tmp.py 
error message not writable 
end of program

As we know, the write ( ) method of file objects needs a string argument. Hence the argument of any other type will result in typeError.

Example

try:
f=open (' testfile . txt' , ' w')
f.write(1234)
except TypeError as e:
print ("error message",e)
print ("end of program")

 

Output

E:\python37>python tmp.py
error message write() argument must be str, not int end of program

Conversely, for file in binary mode, the write () method needs bytes object as argument. If not, same TypeError is raised with different error message.

Example

try:
f =open ( ' testfile . txt' , ' wb' )
f.write('Hello1)
except TypeError as e:
print ("error message",e)
print ("end of program")

Output

E:\python37>python tmp.py
error message a bytes-like object is required, not 'str'
end of program

All file-related functions in the os module raise OSError in the case of invalid or inaccessible file names and paths, or other arguments that have the correct type but are not accepted by the operating system.

Example

import os
try:
fd=os . open ( ' testfile . txt' , os . 0_RD0NLY | os . 0_CREAT) os.write(fd,'Hello'.encode())
except OSError as e:
print ("error message",e)
print ("end of program")

Output

E:\python37>python tmp.py
error message [Errno 9] Bad file descriptor 
end of program

In this chapter, we learned the basic file handling techniques. In the next chapter, we deal with advanced data serialization techniques and special-purpose file storage formats using Python’s built-in modules.