51.Exception Handling in Python

We can make certain mistakes while writing a program that lead to errors when we try to run it. A python program terminates as soon as it encounters an unhandled error. These errors can be broadly classified into two classes:

Syntax errors
Logical errors (Exceptions)

Python Syntax Errors

Error caused by not following the proper structure (syntax) of the language is called syntax error or parsing error.

Let's look at one example:
>>> if a < 3 
 File "<interactive input>", line 1 if a < 3
 ^ 
SyntaxError: invalid syntax

As shown in the example, an arrow indicates where the parser ran into the syntax error.We can notice here that a colon : is missing in the if statement.

Python Logical Errors (Exceptions)

Errors that occur at runtime (after passing the syntax test) are called exceptions or logical errors.

For instance, they occur when we try to open a file(for reading) that does not exist (FileNotFoundError), try to divide a number by zero (ZeroDivisionError), or try to import a module that does not exist (ImportError).

Whenever these types of runtime errors occur, Python creates an exception object. If not handled  properly, it prints a traceback to that error along with some details about why that error occurred.

Let's look at how Python treats these errors:
>>> 3/0
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    3/0
ZeroDivisionError: division by zero
>>> open("abc.txt")
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    open("abc.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'abc.txt'
>>>a=[10,20]
>>> a[2]
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    a[2]
IndexError: list index out of range

Note that Python prints a traceback of where the program was and also print the error message with description.

Python Built-in Exceptions

Illegal operations can raise exceptions. There are plenty of built-in exceptions in Python that are raised when corresponding errors occur. We can view all the built-in exceptions using the built-in local() function as follows:
print(dir(locals()['__builtins__']))

locals()['__builtins__'] will return a module of built-in exceptions, functions, and attributes. dir allows us to list these attributes as strings.

Some of the common built-in exceptions in Python programming along with the error that cause them are listed below:

Exception                        

Cause of Error

AssertionError                  

Raised when an assert statement fails.

AttributeError                   

Raised when attribute assignment or reference fails.

EOFError                          .

Raised when the input() function hits end-of-file condition

FloatingPointError            

Raised when a floating point operation fails.

GeneratorExit                   

Raise when a generator's close() method is called.

ImportError                       

Raised when the imported module is not found.

IndexError                        

Raised when the index of a sequence is out of range.

KeyError                           

Raised when a key is not found in a dictionary.

KeyboardInterrupt            

Raised when the user hits the interrupt key (Ctrl+C or Delete).

MemoryError                    

Raised when an operation runs out of memory.

NameError                        

Raised when a variable is not found in local or global scope.

NotImplementedError      

Raised by abstract methods.

OSError                            

Raised when system operation causes system related error.

OverflowError                  

Raised when the result of an arithmetic operation is too large to be represented.

ReferenceError                

Raised when a weak reference proxy is used to access a garbage collected referent.

RuntimeError                   .

Raised when an error does not fall under any other category

StopIteration                    

Raised by next() function to indicate that there is no further item to be returned by iterator

SyntaxError                     

Raised by parser when syntax error is encountered.

IndentationError              

Raised when there is incorrect indentation.

TabError                          

Raised when indentation consists of inconsistent tabs and spaces.

SystemError                    

Raised when interpreter detects internal error.SystemExitRaised by sys.exit() function.

TypeError                        

Raised when a function or operation is applied to an object of incorrect type.

UnboundLocalError        

Raised when a reference is made to a local variable in a function or method, but no value has been bound to that variable.

UnicodeError                

Raised when a Unicode-related encoding or decoding error occurs.

UnicodeEncodeError      

Raised when a Unicode-related error occurs during encoding.

UnicodeDecodeError      

Raised when a Unicode-related error occurs during decoding.

UnicodeTranslateError   

Raised when a Unicode-related error occurs during translating.

ValueError                      

Raised when a function gets an argument of correct type but improper value.

ZeroDivisionError          

Raised when the second operand of division or modulo operation is zero.










































Python Exception Handling Using try, except and finally statement

Python has many built-in exceptions that are raised when your program encounters an error (something in the program goes wrong).

When these exceptions occur, the Python interpreter stops the current process and passes it to the calling process until it is handled. If not handled, the program will crash.

For example, let us consider a program where we have a  function A that calls function B, which in turn calls function C. If an exception occurs in function C but is not handled in C, the exception passes to B and then to A.

If never handled, an error message is displayed and our program comes to a sudden unexpected halt.

What is Exception?

An exception is an event, which occurs during the execution of a program that disrupts the normal flow of the program's instructions. In general, when a Python script encounters a situation that it cannot cope with, it raises an exception. An exception is a Python object that represents an error.

When a Python script raises an exception, it must either handle the exception immediately otherwise it terminates and quits.

Handling an exception

If you have some suspicious code that may raise an exception, you can defend your program by placing the suspicious code in a try: block. After the try: block, include an except: statement, followed by a block of code which handles the problem as elegantly as possible.

Syntax

Here is simple syntax of try....except...else blocks 
try:
   You do your operations here;
   ......................
except ExceptionI:
   If there is ExceptionI, then execute this block.
except ExceptionII:
   If there is ExceptionII, then execute this block.
   ......................
else:
   If there is no exception then execute this block. 
Here are few important points about the above-mentioned syntax −

A single try statement can have multiple except statements. This is useful when the try block contains statements that may throw different types of exceptions.
You can also provide a generic except clause, which handles any exception.
After the except clause(s), you can include an else-clause. The code in the else-block executes if the code in the try: block does not raise an exception.
The else-block is a good place for code that does not need the try: block's protection.

Sometimes we want to execute an operation that could cause an exception, but we don’t want the program to stop. We can handle the exception using the try and except statements in Python.

In Python, exceptions can be handled using a try statement.The critical operation which can raise an exception is placed inside the try clause. The code that handles the exceptions is written in the except clause.We can thus choose what operations to perform once we have caught the exception. 

For example, we might prompt the user for the name of a file and then try to open it. If the file doesn’t exist, we don’t want the program to crash; we want to handle the exception:

filename =input('Enter a file name: ')
try:
    f = open (filename, "r")
except IOError:
    print ('There is no file named', filename)
else:
    f.close()

As previously mentioned, the portion that can cause an exception is placed inside the try block.

If no exception occurs, the except block is skipped and normal flow continues(for last value). But if any exception occurs, it is caught by the except block.

Example:
try:
    import maths as mp
    print(mp.fact(5))
except ImportError:
    print("Import error module/package not found")

This will generate import error because maths module is not found

The except Clause with No Exceptions
You can also use the except statement with no exceptions defined as follows −
try:
   You do your operations here;
   ......................
except:
   If there is any exception, then execute this block.
   ......................
else:
   If there is no exception then execute this block. 
This kind of a try-except statement catches all the exceptions that occur. Using this kind of try-except statement is not considered a good programming practice though, because it catches all exceptions but does not make the programmer identify the root cause of the problem that may occur.
try:
     x=int(input('Enter x'))
     y=1/x
except:
     print("division by zero")
Note: except: will catch all the errors
try:
     x=int(input('Enter x'))
     y=1/x
except Exception as e: 
     print("Oops!", e.__class__, "occurred.")
     print(e)

Note:Since every exception in Python inherits from the base Exception class, we can also use Exception class to know the type of exception.

Example:
try:
    import maths as mp
    print(mp.fact(5))
except Exception as e:
    print(e.__class__)
<class 'ModuleNotFoundError'>
Note that this will generate ModuleNotFoundError

Catching Multiple Exceptions in Python
The except Clause with Multiple Exceptions
You can also use the same except statement to handle multiple exceptions as follows −

try:
   You do your operations here;
   ......................
except(Exception1[, Exception2[,...ExceptionN]]]):
   If there is any exception from the given exception list, 
   then execute this block.
   ......................
else:
   If there is no exception then execute this block. 
A try clause can have any number of except clauses to handle different exceptions, however, only one will be executed in case an exception occurs.

We can use a tuple of values to specify multiple exceptions in an except clause. Here is an example pseudo code.
try: 
 # do something 
 pass 
except ValueError: 
# handle ValueError exception 
 pass 
 except (TypeError, ZeroDivisionError): 
 # handle multiple exceptions # TypeError and ZeroDivisionError 
 pass
 except:
 # handle all other exceptions pass
Example:
try: 
f = open(filename) 
except (FileNotFoundError, PermissionError): 
print("File Error..")

The try-finally Clause
You can use a finally: block along with a try: block. The finally block is a place to put any code that must execute, whether the try-block raised an exception or not. The syntax of the try-finally statement is this .

try:
   You do your operations here;
   ......................
   Due to any exception, this may be skipped.
finally:
   This would always be executed.
   ......................
You cannot use else clause as well along with a finally clause.

The try statement in Python can have an optional finally clause. This clause is executed no matter what, and is generally used to release external resources.

For example, we may be connected to a remote data center through the network or working with a file or a Graphical User Interface (GUI).

In all these circumstances, we must clean up the resource before the program comes to a halt whether it successfully ran or not. These actions (closing a file, GUI or disconnecting from network) are performed in the finally clause to guarantee the execution.

Here is an example of file operations to illustrate this.
try:
     f = open("test.txt",encoding = 'utf-8') # perform file operations 
except IOError:
    print("file error')
finally: 
     f.close()

This type of construct makes sure that the file is closed even if an exception occurs during the program execution.

Raising Exceptions in Python

In Python programming, exceptions are raised when errors occur at runtime. We can also manually raise exceptions using the raise keyword.

We can optionally pass values to the exception to clarify why that exception was raised.
>>> raise KeyboardInterrupt
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    raise KeyboardInterrupt
KeyboardInterrupt
 >>> raise MemoryError("this is argument")
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    raise MemoryError("this is argument")
MemoryError: this is argument

Example:
try:
    a = int(input("Enter a positive integer: "))
    if a <= 0:
         raise ValueError("That is not a positive number!")
except ValueError as ve:
    print(ve)

Enter a positive integer: -2
That is not a positive number!

Custom Exception Class
In Python, you can create your own custom exception class by inheriting from the built-in Exception class or one of its subclasses. Here's a simple example demonstrating how to create a custom exception class
class CustomError(Exception):
    """Custom exception class."""
    
    def __init__(self, message="A custom error occurred."):
        self.message = message
        super().__init__(self.message)

# Example of using the custom exception
def example_function(value):
    if value < 0:
        raise CustomError("Negative values are not allowed.")

# Using the custom exception in your code
try:
    user_input = int(input("Enter a positive number: "))
    example_function(user_input)
except CustomError as ce:
    print(f"CustomError: {ce}")
except ValueError:
    print("Invalid input. Please enter a valid number.")

In this example, we've created a custom exception class named CustomError that inherits from the built-in Exception class. The __init__ method is used to initialize the exception with an optional custom error message.

The example_function demonstrates how to raise the custom exception based on a specific condition. In this case, if the value passed to the function is negative, it raises a CustomError with a custom error message.

In the try block, we use the example_function within a try-except block. If the function raises a CustomError, we catch it in the except CustomError as ce block and print the custom error message. If the user enters a non-numeric value, a ValueError may occur, and we catch that in the except ValueError block.

Creating custom exception classes allows you to handle specific errors in a more structured way, providing better readability and maintainability in your code.

Example:

Write a program using custom exception class that validates name and age as entered by the user to determine whether the person can cast vote or not.

class invalidAge(Exception):
    def display(self):
        print("Age cannot be below 18 ")
class invalidName(Exception):
    def display(self):
        print("Please Enter a valid name ")


try:
    name = input("Enter the Name :")
    if len(name) == 0:
        raise invalidName()
    age = int(input("Enter the Age :"))
    if age < 18:
         raise invalidAge()
except invalidName as n:
    n.display()
except invalidAge as a:
    a.display()
else:
    print(name, "Congratulations !!! You can vote")

Comments

Popular posts from this blog

Python For Machine Learning - CST 283 - KTU Minor Notes- Dr Binu V P

46.Classes and Objects in Python- Accessors and mutators

KTU Python for machine learning Sample Question Paper and Answer Key Dec 2020