Sunday, December 2, 2018

Exception Handling


Exceptions special objects used by Python to manage errors that arise during a program’s execution. An exception object is created by Python whenever any error occurs that makes Python unsure what to do. If the exceptions are handled by our programs it continues to run otherwise it halts and shows a traceback including a report of the raised exception.

The simple programs usually behave exactly as expected but as programs get bigger and more complex, their behavior becomes harder to predict. For example, a program might have a configuration file: what happens if that file contains settings that are unsuitable or if the file doesn’t exist at all? Almost certainly, programs will use input data that will change over time: what if the data is corrupted or somehow unusable? And what if another system resource, for example, a network connection needed to contact a remote web service to obtain additional data, is unavailable? Worse, what if any number of these abnormal events occur in some method that you use many times throughout your code? Sometimes, you simply don’t care about the error but other times you do: how can your program handle errors intelligently in this scenario?
The answer is through exception handling.

Exception Handling in Python

In Python Exceptions are handled with try-except blocks. A try-except block asks Python to do something, but it also tells Python what to do if an exception is raised. When we use try-except blocks, our programs will continue running even if things start to go wrong. Instead of tracebacks, which can be confusing for users to read, users will see friendly error messages that we write.
Every exception has a certain type. The types in the example are ZeroDivisionError, NameError, and TypeError.

Handling Simple Exceptions

Let’s begin with one of the most basic errors that a program can encounter: trying to divide a number by zero. You’ll see how Python’s own exception handling deals with the problem and how it can be used in quite subtle ways to turn a simple error into intelligent reporting and management. Consider the following example-


class Calculation():
               
                def multiply(self,x,y):                    
                                result = x*y
                                return result
               
                def division(self,a,b):                     
                                result = a/b                        
                                return result
               
               
num1 = int(input('Enter first digit: '))      
num2 = int(input('\nEnter second digit: '))

obj1= Calculation()

mul = obj1.multiply(num1,num2)
print("\nThe multiplied value is: " + str(mul))

div = obj1.division(num1,num2)

print("\nThe divided value is: " + str(div))

When we run this program as shown below we see that an error is reported in the traceback , the  ZeroDivisionError, which is an exception object.


In fact, the report you’re reading is the final stage in a procedure that Python has already gone through to try to handle the underlying problem. Let’s look at that procedure in more detail. As in written arithmetic, the division operator takes two arguments, a dividend to the left and a divisor to the right. The operation is quite simple, but the operator has a number of checks built into it; for example, both of its arguments must be numeric, and the divisor must be nonzero. Usually, these conditions are expected to be satisfied. However, the operator itself has no way of recovering when one of these checks fails. The operator instead falls back on Python’s standard method of dealing with exceptional circumstances, exceptions.

If the divisor is zero, the operator causes an exception object of class ZeroDivisionError to be created. We then say that this exception is raised by the operator (or by the attempted operation). This can be taken to mean “raising” in the sense of discussing a problem at work with your boss and, more widely, in the sense of a problem passing up through a chain of command. When function A calls function B, and function B includes a division by zero, the division operator raises the exception within function B. And if function B doesn’t know how to handle the exception, it raises the exception with function A.

Ultimately in our example, the exception is raised with the interactive mode, which doesn’t know how to handle the circumstances and must therefore raise the exception with you, the programmer. This needs to be done in a human-readable way, which results in the error report. This is usually called a traceback, which we’ll discuss in more detail later.

Some programming languages talk of throwing and catching exceptions rather than raising them: the division operator throws an exception up to function B; if function B doesn’t catch that exception, it carries on to function A. This is just another metaphor for the same underlying process.
Python creates an exception object in response to a situation where it can’t do what we ask it to. When this happens, Python stops the program and tells us the kind of exception that was raised. We can use this information to modify our program. We’ll tell Python what to do when this kind of exception occurs; that way, if it happens again, we’re prepared. We use the try-except block to tell Python what to do when this kind of exception occurs.

Using the try Statement with an except Clause in Python

Let's discuss try...except in detail. First see the syntax:

try:
operation block;
except Exception_name:
If there is Exception_name1 then execute this block.
except Exception_name2:
If there is Exception_name2, then execute this block.
else:
If there is no exception then execute this block.


If the exception is created deep in a program (e.g., within a method), it can be caught and dealt with in between the original error and the program’s output. Python’s try statement is used to deal with
exceptions, and in its most basic form, it can be used as shown in the code below where we use a try-except block for handling the ZeroDivisionError exception.

def division(self,a,b):
                               
                                try:
                                               
                                                result = a/b
                               
                                                return result
                                               
                                except:
                                               
                                                print("\nOops! You can't divide by zero!")

We put the lines that caused the error, inside a try block. If the code in a try block works, Python skips over the except block. If the code in the try block causes an error, Python looks for an except block whose error matches the one that was raised and runs the code in that block.

In this example, the code in the try block produces a ZeroDivisionError, so Python looks for an except block telling it how to respond. Python then runs the code in that block, and the user sees a friendly error message instead of a traceback.

After making the necessary changes in the program when we run it we get the output as shown below:

 

The try-except-else block works like this: Python attempts to run the code in the try statement. The only code that should go in a try statement is code that might cause an exception to be raised. Sometimes you’ll have additional code that should run only if the try block was successful; this code goes in the else block. The except block tells Python what to do in case a certain exception arises when it tries to run the code in the try statement.

Thus we can modify our program to include an else block as shown below-

def division(self,a,b):
                               
                                try:
                                               
                                                result = a/b
                               
                                                return result
                                               
                                except:
                                               
                                                print("\nOops! You can't divide by zero!")
                                               
                                else:
                                               
                                                print(result)

By anticipating likely sources of errors, you can write robust programs that continue to run even when they encounter invalid data and missing resources. Your code will be resistant to innocent user mistakes and malicious attacks.

In the given program, when we give the input from the keyboard, the input string will be
converted into int type. When we supply a string instead of a number, then the interpreter returns a message with a ValueError error as shown below:


When number 0 is supplied, then ZeroDivisionError is returned. By using multiple exception blocks, we can handle both the exceptions. See the program below:

class Calculation():
               
                def multiply(self,x,y):
                               
                                result = x*y
                                return result
               
                def division(self,a,b):
                               
                                try:
                                               
                                                result = a/b
                               
                                                return result      
                               
                               
                                except ZeroDivisionError:
                                               
                                                print("\nOops! You can't divide by zero!")                                                            
                                               
                                               
                                else:
                                               
                                                print(result)
               
try:
                               

                num1 = int(input('Enter first digit: '))      
                num2 = int(input('\nEnter second digit: '))
               


                obj1= Calculation()

                mul = obj1.multiply(num1,num2)
                print("\nThe multiplied value is: " + str(mul))

                div = obj1.division(num1,num2)

                print("\nThe divided value is: " + str(div))
               
except ValueError:
                                               
                                                               
                                                print("\nYou did't input int value!")


Now if we run the program with a non-integer input the ValueError exception will be raised which is handled by the program. See the output below:



In the preceding output, customized message, “You did't input int value!" has been displayed so that the user can understand his mistake.

Now suppose by mistake we changed a variable name or used an undefined variable  in our program as shown below:

num1 = int(input('Enter first digit: '))      
num2 = int(input('\nEnter second digit: '))

                obj1= Calculation()

                mul = obj1.multiply(num1,num)
                print("\nThe multiplied value is: " + str(mul))


Instead of passing num2, I passed num as a parameter in the multiply(). See the output below:



We can handle this NameError exception also in our program as shown below:

try:
                               

                num1 = int(input('Enter first digit: '))      
                num2 = int(input('\nEnter second digit: '))


                obj1= Calculation()

                mul = obj1.multiply(num1,num)
                print("\nThe multiplied value is: " + str(mul))

                div = obj1.division(num1,num2)

                print("\nThe divided value is: " + str(div))
               
except ValueError:
               
                print("\nYou did't input int value!")
                                               
except NameError:
               
                print("\nCheck the variable names you used!")


The output is shown below:

 

Another common issue when working with files is handling missing files. The file you’re looking for might be in a different location, the filename may be misspelled, or the file may not exist at all. You can handle all of these situations in a straightforward way with a try-except block.

Let’s try to read a file that doesn’t exist. The following program tries to read the cities from the file cities.txt but I haven’t saved the file cities.txt in the same directory as city.py:

filename = 'cities.txt'
with open(filename) as file_obj:
                contents = file_obj.read()
                print(contents)

Python can’t read from a missing file, so it raises an exception as shown below:



The last line of the traceback reports a FileNotFoundError: this is the exception Python creates when it can’t find the file it’s trying to open. In this example, the open() function produces the error, so to handle it, the try block will begin just before the line that contains open():

filename = 'cities.txt'

try:
               
                with open(filename) as file_obj:
                                contents = file_obj.read()
                               
except FileNotFoundError:
               
                print("\nThe file "+ filename + " doesn't exists!")
                               
else:
               
                                               
                print(contents)


In this example, the code in the try block produces a FileNotFoundError, so Python looks for an except block that matches that error. Python then runs the code in that block, and the result is a friendly error message instead of a traceback:

 

If I move the file cities.txt to the same directory as that of cities.py, the content of this file will be printed as shown below:



The try...finally statement

In a situation where you are completely sure that a certain block of code will be executed whether the program throws exceptions or not, try...finally is useful. Consider the situation when you open a file and read the input, but for some reason the program throws an exception and the file you want is closed whether the exception occurs or not, then try...finally will solve the problem.

The syntax is as follows:

try:
#run this action first
except:
# Run if exception occurs
Finally :
#Always run this code

The order of the statement should be: try -> except -> else -> finally. See the following program-

filename = 'cities1.txt'

try:
               
                with open(filename) as file_obj:
                                contents = file_obj.read()
                               
except FileNotFoundError:
               
                print("\nThe file "+ filename + " doesn't exists!")
                               
else:
               
                                               
                print(contents)


finally:
               
                print("\nProgram ends!")

The following program tries to read the cities from the file cities1.txt but I haven’t saved the file cities1.txt in the same directory as city.py. Python can’t read from a missing file, so it raises an exception as shown below:



In the preceding output the try, except and finally blocks have been executed.


The exception argument

When you write a program, it is very mundane and tedious to write each and every exception type. Instead of writing each exception, you could use just one line. See the program below:

filename = 'cities1.txt'

try:
               
                with open(filename) as file_obj:
                                contents = file_obj.read()
                               
except Exception as e:
               
                print(e,type(e))
                               
else:
               
                                               
                print(contents)


finally:
               
                print("\nProgram ends!")


The preceding code catches the exception as e and type(e) displays its exception type. Let's see the output. In the code, e is the argument of the exception: the contents referred by the argument vary by exception:



User-defined exceptions

Python allows you to define your own exceptions. Exceptions should be inherited from the Exception class. See the example below:


class MyException(Exception):
               
                def __init__(self, value):
                                self.value = value
                def __str__(self):
                                return (self.value)
try:
                num = input("Enter the number : ")
                if num == '2':
                                raise MyException("ohh")
                else :
                                print ("\nnumber is not 2")

except MyException :
                print ("\nMy exception occurred")


The preceding code defined the MyException class, which inherits the base class Exception. In this example, the default __init__() exception has been overridden.  The code in the try block raises the user-defined exception if you pass the value 2. The raised exception is handled by the except block. The raise statement allows the programmer to trigger specific exceptions explicitly. See the output of the program:



In the preceding code, if you pass the value 2, then it gives a user-defined custom error. If you pass a number other than 2, then no error occurs.

Without handling exceptions you cannot write standard code so it’s better to develop a habit of incorporating exception handling while coding. This is the end of today’s topic. Till we meet next keep practicing and learning Python as Python is easy to learn!



Share:

0 comments:

Post a Comment