Errors and exceptions in Python 3

There are two kinds of errors:

  • Syntax errors, where the program contains invalid syntax
  • Exceptions, errors that occur during runtime, though the syntax is valid

The list of built-in exceptions can be found here.

Syntax errors can be avoided by writing valid code and exceptions can be handled using try and except. An example of handling an exception:

def division(num1, num2):
    """Return num1/num2"""
    return num1/num2

try:
    print(division(10,0))
except ZeroDivisionError:
    print("You can't divide by zero")
#output
You can't divide by zero

How try and except statements work:

  • The code inside the try block is executed
  • If no exception occurs, the except block is skipped
  • If an exception occurs, execution of remaining try block is skipped and the except block matching the raised exception is executed. If the exception has not been handled, it is passed to an outer try block. If there is no outer try block, it is an unhandled exception and execution stops.

In the above example, note that I am not handling the exception inside the division function. Instead I am handling the exception when the function is called.

An except clause can handle multiple exceptions using the following syntax:

except Exception1, Exception2 :
    #code to handle Exception1 and Exception2

An exception can be raised as follows:

raise ExceptionName(args)

The arguments are optional. If an exception class is present, it is instantiated by calling its constructor without arguments.

A class in an except clause is compatible with an exception if it is the same class or a base class thereof. The following example demonstrates this concept:

class Base(Exception):
    """Base class"""
    pass

class Derived(Base):
    """Inherits from Base class"""
    pass

for to_raise in [Base,Derived]:
    try:
        raise to_raise()
    except Derived:
        print("Derived class")
    except Base:
        print("Base class")

#output
Base class
Derived class

for to_raise in [Base,Derived]:
    try:
        raise to_raise()
    except Base:
        print("Base class")
    except Derived:
        print("Derived class")

#output
Base class
Base class

It is possible to catch wildcard exceptions as follows:

try:
    raise Exception("An exception occurred")
except:
    print("All exceptions are caught here")
#output
All exceptions are caught here

So every exception that is raised inside the try block is caught by the wildcard except by omitting the exception names. This should be avoided since it can hide any real programming errors.

An optional else clause is available, which executes the code inside when no exception is raised. Example:

try:
    #some code
except ExceptionName:
    #handle exception
else:
    #executed when exception is not raised inside try

When an exception is raised, it is possible to have data associated with the exception. This is useful for debugging purposes to understand under what circumstances the exception was raised. The arguments passed while raising an exception depend on the type of exception. The following is an example:

try:
    raise Exception('arg1', 'arg2')
except Exception as inst:
    print(type(inst))
    print(inst.args)
    print(inst)

    x,y = inst.args #unpacking arguments
    print("x = {} y = {}".format(x,y))
#output
<class 'Exception'>
('arg1', 'arg2')
('arg1', 'arg2')
x = arg1 y = arg2

It is possible to have user defined exceptions in Python by creating a new class. The new class has to be derived from The Exception class either directly or indirectly. Though it is possible for exception classes to do anything a normal class can do, they are usually kept simple by only having a few attributes that contain information about the exception.

If a module can raise different custom exceptions, it is a good practice to have a base class that inherits from Exception and other classes that raise exceptions inherit from the base class. The following is a simple example:

class Error(Exception):
    """Base class for all exceptions in the module"""
    name = "exceptions.py"

class FirstError(Error):
    """Specific exception class inherits from Error"""
    
    def __init__(self,message):
        self.message = message

try:
    raise FirstError("First custom exception")
except FirstError as custom_exception:
    print("FirstError has been raise with message: ",custom_exception.message, "\ncurrent module: ", custom_exception.name)

#output
FirstError has been raise with message:  First custom exception
current module:  exceptions.py

It’s standard practice to have all exception classes end with Error.

A finally clause is available which can be used to define clean up actions. It is always executed, whether an exception occurred or not. The following is an example:

def proper_divide(x,y):
    """Returns x/y and handles exceptions"""
    try:
        result = x/y
    except ZeroDivisionError:
        print("Division by zero handled")
    else:
        print("Result is ", result)
    finally:
        print("This is always executed")

proper_divide(10,5)
#output
Result is  2.0
This is always executed
proper_divide(10,0)
#output
Division by zero handled
This is always executed

The above example also shows how to use try, except, else and finally together.

Code for today’s plog can be found here.

References: